Kompatible Antwort
Es gibt viele verschiedene Möglichkeiten, dies zu tun in bash .
Es ist jedoch wichtig, zunächst festzustellen, dass bash
hat viele Spezial Merkmale (sogenannte Bashismen ), die in keinem anderen Programm funktionieren Shell .
Im Besonderen, Arrays , assoziative Arrays und Mustersubstitution die in den Lösungen in diesem Beitrag und in anderen Beiträgen verwendet werden, sind Bashismen und funktioniert möglicherweise nicht unter anderen Muscheln die viele Menschen nutzen.
Zum Beispiel: auf meinem Debian GNU/Linux gibt es eine Standard Shell genannt Bindestrich Ich kenne viele Leute, die gerne eine andere Shell benutzen, die ksh ; und es gibt auch ein spezielles Werkzeug namens busybox mit seinem eigenen Shell-Interpreter ( Asche ).
Gewünschte Zeichenfolge
Die aufzuteilende Zeichenfolge in der obigen Frage lautet:
IN="bla@some.com;john@home.com"
Ich werde eine geänderte Version dieser Zeichenkette verwenden, um sicherzustellen, dass meine Lösung robust gegenüber Zeichenketten mit Leerzeichen ist, was bei anderen Lösungen zu Problemen führen könnte:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
Zeichenkette anhand des Begrenzungszeichens in bash (Version >=4.2)
Unter rein bash
können wir eine Array mit Elementen, die durch einen temporären Wert für IFS (die Eingabefeldtrennzeichen ). Der IFS sagt unter anderem bash
welche(s) Zeichen bei der Definition eines Arrays als Trennzeichen zwischen den Elementen verwendet werden soll:
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
Wenn einem Befehl eine IFS-Definition vorangestellt wird, ändert sich der IFS für diesen Befehl. nur und setzt ihn unmittelbar danach wieder auf den vorherigen Wert zurück. Das bedeutet, dass wir das oben Genannte in nur einer Zeile erledigen 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
wurde in einem Array mit dem Namen fields
, getrennt durch die Semikolons:
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 anzeigen, indem wir 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 read
ist die schnellste Art und Weise, den Split durchzuführen, weil es keine Gabeln oder externe Ressourcen aufgerufen.
Sobald das Array definiert ist, können Sie eine einfache Schleife verwenden, um jedes Feld (oder besser gesagt, jedes Element in dem Array, das Sie jetzt definiert haben) zu 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önnten jedes Feld nach der Verarbeitung aus dem Array löschen, indem Sie eine wechseln. Ansatz, der 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 nicht einmal eine Schleife darüber laufen lassen:
printf "> [%s]\n" "${fields[@]}"
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Update: kürzlich bash >= 4.4
In neueren Versionen von bash
können Sie auch mit dem Befehl mapfile
:
mapfile -td \; fields < <(printf "%s\0" "$IN")
Bei dieser Syntax bleiben Sonderzeichen, Zeilenumbrüche und leere Felder erhalten!
Wenn Sie keine leeren Felder einfügen möchten, können Sie wie folgt vorgehen:
mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}") # drop '\n' added by '<<<'
Mit mapfile
können Sie auch auf die Deklaration eines Arrays verzichten und implizit eine Schleife über die abgegrenzten Elemente ziehen, indem Sie für jedes Element 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
(Anmerkung: Die \0
am Ende der Formatzeichenkette ist nutzlos, wenn Sie sich nicht um leere Felder am Ende der Zeichenkette 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 könnten Sie verwenden <<<
und fügen Sie in den Funktionskörper eine Verarbeitung ein, um den hinzugefügten Zeilenumbruch zu entfernen:
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.
Zeichenkette anhand des Begrenzungszeichens in Shell
Wenn Sie nicht verwenden können bash
oder wenn Sie etwas schreiben wollen, das in vielen verschiedenen Shells verwendet werden kann, können Sie oft kann nicht verwenden. Bashismen -- und dazu gehören auch die Arrays, die wir in den obigen Lösungen verwendet haben.
Wir müssen jedoch keine Arrays verwenden, um eine Schleife über "Elemente" einer Zeichenkette zu ziehen. Es gibt eine Syntax, die in vielen Shells verwendet wird, um Teilstrings einer Zeichenkette aus der erste o zuletzt Auftreten eines Musters. Beachten Sie, dass *
ist ein Platzhalter, der für null oder mehr Zeichen steht:
(Das Fehlen dieses Ansatzes in allen bisher veröffentlichten Lösungen 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 Ergebnis_Unter :
#
y %
die kürzestmögliche übereinstimmende Teilzeichenkette aus der Datei Start y Ende der Zeichenkette, und
##
y %%
die längste übereinstimmende Teilzeichenkette löschen.
Mit der obigen Syntax können wir einen Ansatz erstellen, bei dem wir Teilstrings "Elemente" aus der Zeichenkette extrahieren, indem wir die Teilstrings bis zum oder nach dem Begrenzer löschen.
Der folgende Codeblock funktioniert gut in bash (einschließlich Mac OS's bash
), Bindestrich , ksh und busybox 's Asche :
(Dank an Adam Katz 's Kommentar Dadurch wird diese Schleife viel einfacher!)
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$IN" != "$iter" ] ;do
# extract the substring from start of string up to delimiter.
iter=${IN%%;*}
# delete this first "element" AND next separator, from $IN.
IN="${IN#$iter;}"
# Print (or doing anything with) the first "element".
echo "> [$iter]"
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Viel Spaß!
30 Stimmen
In Bezug auf Ihr "Edit2": Sie können einfach "IFS aufheben" und der Standardzustand wird wiederhergestellt. Es besteht keine Notwendigkeit, ihn explizit zu speichern und wiederherzustellen, es sei denn, Sie haben Grund zu der Annahme, dass er bereits auf einen anderen Wert als den Standardwert gesetzt wurde. Wenn Sie dies innerhalb einer Funktion tun (und wenn Sie es nicht tun, warum nicht?), können Sie IFS als lokale Variable setzen, die nach dem Verlassen der Funktion auf ihren vorherigen Wert zurückgesetzt wird.
33 Stimmen
@BrooksMoses: (a) +1 für die Verwendung
local IFS=...
wenn möglich; (b) -1 fürunset IFS
wird IFS nicht genau auf seinen Standardwert zurückgesetzt, obwohl ich glaube, dass sich ein nicht gesetzter IFS genauso verhält wie der Standardwert von IFS ($' \t\n '), aber es scheint eine schlechte Praxis zu sein, blind davon auszugehen, dass Ihr Code niemals mit einem benutzerdefinierten IFS-Wert aufgerufen werden wird; (c) eine andere Idee ist, eine Subshell aufzurufen:(IFS=$custom; ...)
Wenn die Subshell beendet wird, kehrt IFS zu dem ursprünglichen Zustand zurück.0 Stimmen
Ich möchte nur einen kurzen Blick auf die Pfade werfen, um zu entscheiden, wohin ich eine ausführbare Datei werfen soll, also habe ich auf die Ausführung von
ruby -e "puts ENV.fetch('PATH').split(':')"
. Wenn Sie reine Bash bleiben wollen, hilft es nicht, wenn Sie eine beliebige Skriptsprache die eine eingebaute Aufteilung hat, ist einfacher.0 Stimmen
Dies ist eine Art Kommentar im Vorbeifahren, aber da der OP E-Mail-Adressen als Beispiel verwendet hat, hat sich jemand die Mühe gemacht, die Frage so zu beantworten, dass sie vollständig RFC 5322-konform ist, d. h., dass jede Zeichenkette in Anführungszeichen vor dem @ erscheinen kann, was bedeutet, dass Sie reguläre Ausdrücke oder eine andere Art von Parser benötigen, anstatt die naive Verwendung von IFS oder anderen simplen Splitterfunktionen.
13 Stimmen
for x in $(IFS=';';echo $IN); do echo "> [$x]"; done
3 Stimmen
Um es als Array zu speichern, musste ich einen weiteren Satz von Klammern setzen und die
\n
für nur ein Leerzeichen. Die letzte Zeile lautet alsomails=($(echo $IN | tr ";" " "))
. Jetzt kann ich also die Elemente vonmails
durch Verwendung der Array-Notationmails[index]
oder einfach in einer Schleife iterieren0 Stimmen
Was auch immer es wert ist, die
tr
Lösung funktioniert in zsh nicht auf die gleiche Weise.0 Stimmen
Für
$IFS
siehe Was ist die genaue Bedeutung vonIFS=$'\n'
0 Stimmen
Sie brauchen weder IFS zu speichern/wiederherzustellen, noch müssen Sie "local" verwenden. ``` declare -a OUT; IFS=';' OUT=( $( echo "$IN") ) ``` ist alles, was Sie brauchen (wie von @user2037659 angedeutet). Der Schlüssel ist, dass Sie Umgebungsvariablen lokal für einen einzelnen Befehl setzen können, indem Sie die Zuweisung(en) vor den Befehl setzen. (Ersetzen Sie das erste ';' durch einen Zeilenumbruch, um die Lesbarkeit zu verbessern). IFS= ist ein besonders nützlicher Fall.