2924 Stimmen

Wie splitte ich eine Zeichenkette an einem Begrenzer in der Bash?

Ich habe diese Zeichenfolge in einer Variablen gespeichert:

IN="bla@some.com;john@home.com"

Nun möchte ich die Zeichenketten aufteilen nach ; Begrenzungszeichen, so dass ich habe:

ADDR1="bla@some.com"
ADDR2="john@home.com"

Ich brauche nicht unbedingt die ADDR1 y ADDR2 Variablen. Wenn sie Elemente eines Arrays sind, ist das sogar noch besser.


Nach den Vorschlägen aus den nachstehenden Antworten kam ich zu folgendem Ergebnis, das dem entspricht, was ich wollte:

#!/usr/bin/env bash

IN="bla@some.com;john@home.com"

mails=$(echo $IN | tr ";" "\n")

for addr in $mails
do
    echo "> [$addr]"
done

Ausgabe:

> [bla@some.com]
> [john@home.com]

Es gab eine Lösung, die die Einstellung Interner_Feld_Trenner (IFS) an ; . Ich bin mir nicht sicher, was mit dieser Antwort passiert ist, wie setzt man IFS zurück zum Standard?

RE: IFS Lösung, ich habe das ausprobiert und es funktioniert, ich behalte die alte IFS und dann wiederherstellen:

IN="bla@some.com;john@home.com"

OIFS=$IFS
IFS=';'
mails2=$IN
for x in $mails2
do
    echo "> [$x]"
done

IFS=$OIFS

Übrigens, als ich versuchte

mails2=($IN)

Ich habe nur die erste Zeichenkette erhalten, wenn ich sie in einer Schleife ohne Klammern ausgedruckt habe $IN es funktioniert.

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ür unset 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.

44voto

nickjb Punkte 1056

Eine andere Sichtweise auf Darrons Antwort So mache ich es:

IN="bla@some.com;john@home.com"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)

0 Stimmen

Ich glaube schon! Führen Sie die Befehle oben und dann "echo $ADDR1 ... $ADDR2" und ich bekomme "bla@some.com ... john@home.com" Ausgabe

1 Stimmen

Dies funktionierte WIRKLICH gut für mich... Ich benutzte es, um über ein Array von Zeichenfolgen zu iterieren, die kommagetrennte DB,SERVER,PORT-Daten enthielten, um mysqldump zu verwenden.

5 Stimmen

Diagnose: Die IFS=";" Zuordnung existiert nur in der $(...; echo $IN) Subshell; deshalb denken einige Leser (mich eingeschlossen) zunächst, dass es nicht funktionieren wird. Ich nahm an, dass das gesamte $IN von ADDR1 aufgesaugt wurde. Aber nickjb hat recht; es funktioniert. Der Grund dafür ist, dass echo $IN parst seine Argumente unter Verwendung des aktuellen Werts von $IFS, gibt sie dann aber mit einem Leerzeichen als Echo an stdout aus, unabhängig von der Einstellung von $IFS. Der Nettoeffekt ist also so, als hätte man den Befehl read ADDR1 ADDR2 <<< "bla@some.com john@home.com" (beachten Sie, dass die Eingabe durch Leerzeichen getrennt ist, nicht durch ;-).

41voto

Darron Punkte 20861

Wie wäre es mit diesem einen Satz, wenn Sie keine Arrays verwenden:

IFS=';' read ADDR1 ADDR2 <<<$IN

1 Stimmen

Erwägen Sie die Verwendung von read -r ... um sicherzustellen, dass z. B. die beiden Zeichen " \t " in der Eingabe als dieselben zwei Zeichen in Ihren Variablen erscheinen (anstelle eines einzelnen Tabulatorzeichens).

0 Stimmen

-1 Das funktioniert hier nicht (ubuntu 12.04). Hinzufügen von echo "ADDR1 $ADDR1"\n echo "ADDR2 $ADDR2" in Ihr Snippet einfügt, wird Folgendes ausgegeben ADDR1 bla@some.com john@home.com\nADDR2 ( \n ist ein Zeilenumbruch)

1 Stimmen

Dies ist wahrscheinlich auf einen Fehler zurückzuführen, der IFS und hier Strings, die in bash 4.3. Zitat $IN sollte das Problem beheben. (Theoretisch, $IN unterliegt nicht der Worttrennung oder dem Globbing, nachdem es expandiert ist, was bedeutet, dass die Anführungszeichen unnötig sein sollten. Sogar in 4.3 gibt es noch mindestens einen Fehler, der gemeldet wurde und behoben werden soll, so dass Anführungszeichen weiterhin eine gute Idee sind).

40voto

gniourf_gniourf Punkte 41590

In Bash funktioniert das auch, wenn die Variable Zeilenumbrüche enthält:

IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")

Sehen Sie:

$ in=$'one;two three;*;there is\na newline\nin this field'
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two three" [2]="*" [3]="there is
a newline
in this field")'

Der Trick, damit dies funktioniert, ist die Verwendung der -d Option von read (Begrenzungszeichen) mit einem leeren Begrenzungszeichen, so dass read ist gezwungen, alles zu lesen, was ihm vorgesetzt wird. Und wir füttern read mit genau dem Inhalt der Variablen in ohne abschließenden Zeilenumbruch dank der printf . Beachten Sie, dass wir auch das Trennzeichen in printf um sicherzustellen, dass die Zeichenkette, die an read hat ein nachgestelltes Begrenzungszeichen. Ohne dieses, read würde mögliche leere Felder am Ende abschneiden:

$ in='one;two;three;'    # there's an empty field
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two" [2]="three" [3]="")'

wird das nachgestellte leere Feld beibehalten.


Aktualisierung für Bash4.4

Seit Bash 4.4 ist das eingebaute mapfile (alias readarray ) unterstützt die -d um ein Trennzeichen anzugeben. Daher ist eine andere kanonische Weise:

mapfile -d ';' -t array < <(printf '%s;' "$in")

5 Stimmen

Ich habe diese Lösung als einzige auf der Liste gefunden, die korrekt funktioniert mit \n , Räume und * gleichzeitig. Auch keine Schleifen; die Array-Variable ist nach der Ausführung in der Shell zugänglich (im Gegensatz zur am höchsten bewerteten Antwort). Anmerkung, in=$'...' funktioniert es nicht mit doppelten Anführungszeichen. Ich denke, es braucht mehr Upvotes.

0 Stimmen

El mapfile Beispiel scheitert, wenn ich Folgendes verwenden möchte % als Begrenzungszeichen. Ich schlage vor printf '%s' "$in%" .

34voto

Emilien Brigand Punkte 8323

Ohne die Einstellung des IFS

Wenn Sie nur einen Dickdarm haben, können Sie das tun:

a="foo:bar"
b=${a%:*}
c=${a##*:}

erhalten Sie:

b = foo
c = bar

24voto

kenorb Punkte 134883

Hier ist ein sauberer 3-Liner:

in="foo@bar;bizz@buzz;fizz@buzz;buzz@woof"
IFS=';' list=($in)
for item in "${list[@]}"; do echo $item; done

wobei IFS Wörter anhand des Trennzeichens abgrenzen und () wird verwendet, um eine Array . Dann [@] wird verwendet, um jedes Element als separates Wort zurückzugeben.

Wenn Sie danach noch einen Code haben, müssen Sie auch die $IFS z.B. unset IFS .

6 Stimmen

Die Verwendung von $in unquoted erlaubt die Erweiterung von Wildcards.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X