Kompatible Antwort
Es gibt viele verschiedene Möglichkeiten, dies zu tun Bash.
Allerdings ist es wichtig, zuerst zu beachten Sie, dass bash
viele hat spezielle Eigenschaften (so genannte bashisms ) das wird nicht funktionieren in jedem anderenSchale.
Insbesondere Arrays , assoziative Arrays und Mustersubstitutionen , die in den Lösungen in diesem Beitrag sowie in anderen im Thread verwendet werden, sind Bashismen und funktionieren möglicherweise nicht unter anderen Shells , die von vielen Personen verwendet werden.
Zum Beispiel: Auf meinem Debian GNU / Linux gibt es eine Standard- Shell namensStrich;; Ich kenne viele Leute, die gerne eine andere Shell namens verwendenksh;; und es gibt auch ein spezielles Werkzeug namensBusybox mit seinem eigenen Shell-Interpreter (Asche).
Angeforderte Zeichenfolge
Die in der obigen Frage zu teilende Zeichenfolge lautet:
IN="bla@some.com;john@home.com"
Ich werde eine modifizierte Version dieser Zeichenfolge verwenden, um sicherzustellen, dass meine Lösung gegenüber Zeichenfolgen mit Leerzeichen robust ist, wodurch andere Lösungen beschädigt werden können:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
Teilen Sie die Zeichenfolge basierend auf dem Trennzeichen in Bash (Version> = 4.2)
In rein bash
können wir ein Array mit Elementen erstellen, die durch einen temporären Wert für IFS (das Eingabefeldtrennzeichen ) getrennt sind. Das IFS gibt unter anderem an, bash
welche Zeichen bei der Definition eines Arrays als Trennzeichen zwischen Elementen behandelt werden sollen:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# save original IFS value so we can restore it later
oIFS="$IFS"
IFS=";"
declare -a fields=($IN)
IFS="$oIFS"
unset oIFS
In neueren Versionen von bash
, einen Befehl mit einer IFS - Definition voran ändert die IFS für diesen Befehl nur und setzt sie auf den vorherigen Wert unmittelbar danach. Dies bedeutet, dass wir das Obige in nur einer Zeile tun können:
IFS=\; read -a fields <<<"$IN"
# after this command, the IFS resets back to its previous value (here, the default):
set | grep ^IFS=
# IFS=$' \t\n'
Wir können sehen, dass die Zeichenfolge IN
in einem Array mit dem Namen gespeichert wurde fields
, das auf die Semikolons aufgeteilt ist:
set | grep ^fields=\\\|^IN=
# fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
# IN='bla@some.com;john@home.com;Full Name <fulnam@other.org>'
(Wir können den Inhalt dieser Variablen auch mit anzeigen declare -p
:)
declare -p IN fields
# declare -- IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# declare -a fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
Beachten Sie, dass dies read
der schnellste Weg ist, um die Aufteilung durchzuführen, da keine Gabeln oder externen Ressourcen aufgerufen werden.
Sobald das Array definiert ist, können Sie jedes Feld (oder vielmehr jedes Element in dem Array, das Sie jetzt definiert haben) mit einer einfachen Schleife verarbeiten:
# `"${fields[@]}"` expands to return every element of `fields` array as a separate argument
for x in "${fields[@]}" ;do
echo "> [$x]"
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Oder Sie können jedes Feld nach der Verarbeitung mit einem Shifting- Ansatz aus dem Array entfernen , was mir gefällt:
while [ "$fields" ] ;do
echo "> [$fields]"
# slice the array
fields=("${fields[@]:1}")
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Und wenn Sie nur einen einfachen Ausdruck des Arrays wünschen, müssen Sie es nicht einmal durchlaufen:
printf "> [%s]\n" "${fields[@]}"
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Update: aktuell Bash > = 4,4
In neueren Versionen von bash
können Sie auch mit dem folgenden Befehl spielen mapfile
:
mapfile -td \; fields < <(printf "%s\0" "$IN")
Diese Syntax bewahrt Sonderzeichen, Zeilenumbrüche und leere Felder!
Wenn Sie keine leeren Felder einfügen möchten, können Sie Folgendes tun:
mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}") # drop '\n' added by '<<<'
Mit mapfile
können Sie auch das Deklarieren eines Arrays überspringen und implizit die begrenzten Elemente "durchlaufen", indem Sie jeweils eine Funktion aufrufen:
myPubliMail() {
printf "Seq: %6d: Sending mail to '%s'..." $1 "$2"
# mail -s "This is not a spam..." "$2" </path/to/body
printf "\e[3D, done.\n"
}
mapfile < <(printf "%s\0" "$IN") -td \; -c 1 -C myPubliMail
(Hinweis: Das \0
am Ende der Formatzeichenfolge ist nutzlos, wenn Sie sich nicht um leere Felder am Ende der Zeichenfolge kümmern oder diese nicht vorhanden sind.)
mapfile < <(echo -n "$IN") -td \; -c 1 -C myPubliMail
# Seq: 0: Sending mail to 'bla@some.com', done.
# Seq: 1: Sending mail to 'john@home.com', done.
# Seq: 2: Sending mail to 'Full Name <fulnam@other.org>', done.
Oder Sie könnten verwenden <<<
und in den Funktionskörper eine Verarbeitung aufnehmen, um die hinzugefügte neue Zeile zu löschen:
myPubliMail() {
local seq=$1 dest="${2%$'\n'}"
printf "Seq: %6d: Sending mail to '%s'..." $seq "$dest"
# mail -s "This is not a spam..." "$dest" </path/to/body
printf "\e[3D, done.\n"
}
mapfile <<<"$IN" -td \; -c 1 -C myPubliMail
# Renders the same output:
# Seq: 0: Sending mail to 'bla@some.com', done.
# Seq: 1: Sending mail to 'john@home.com', done.
# Seq: 2: Sending mail to 'Full Name <fulnam@other.org>', done.
Teilen Sie die Zeichenfolge basierend auf dem Trennzeichen in Schale
Wenn Sie nicht verwenden können bash
, oder wenn Sie etwas schreiben wollen , die in vielen verschiedenen Muscheln verwendet werden können, können Sie oft nicht verwenden bashisms - und dazu gehören auch die Arrays wir oben haben in den Lösungen.
Wir müssen jedoch keine Arrays verwenden, um "Elemente" eines Strings zu durchlaufen. In vielen Shells wird eine Syntax zum Löschen von Teilzeichenfolgen einer Zeichenfolge aus dem ersten oder letzten Auftreten eines Musters verwendet. Beachten Sie, dass*
ein Platzhalter ist, der für null oder mehr Zeichen steht:
(Das Fehlen dieses Ansatzes in einer bisher veröffentlichten Lösung ist der Hauptgrund, warum ich diese Antwort schreibe;)
${var#*SubStr} # drops substring from start of string up to first occurrence of `SubStr`
${var##*SubStr} # drops substring from start of string up to last occurrence of `SubStr`
${var%SubStr*} # drops substring from last occurrence of `SubStr` to end of string
${var%%SubStr*} # drops substring from first occurrence of `SubStr` to end of string
Wie von Score_Under erklärt :
#
und %
löschen Sie den kürzestmöglichen übereinstimmenden Teilstring vom Anfang bzw. Ende des Strings, und
##
und %%
löschen Sie den längstmöglichen passenden Teilstring.
Mit der obigen Syntax können wir einen Ansatz erstellen, bei dem wir Teilstring- "Elemente" aus der Zeichenfolge extrahieren, indem wir die Teilzeichenfolgen bis zum oder nach dem Trennzeichen löschen.
Der folgende Codeblock funktioniert gut in Bash (einschließlich Mac OS bash
),Strich, ksh, und Busybox's Asche::
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$IN" ] ;do
# extract the substring from start of string up to delimiter.
# this is the first "element" of the string.
iter=${IN%%;*}
echo "> [$iter]"
# if there's only one element left, set `IN` to an empty string.
# this causes us to exit this `while` loop.
# else, we delete the first "element" of the string from IN, and move onto the next.
[ "$IN" = "$iter" ] && \
IN='' || \
IN="${IN#*;}"
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Habe Spaß!