Die anderen Antworten werden brechen , wenn Ausgabe von Kommando Leerzeichen enthält (was ziemlich häufig ist) oder glob Zeichen wie *
, ?
, [...]
.
Um die Ausgabe eines Befehls in einem Array mit einer Zeile pro Element zu erhalten, gibt es im Wesentlichen drei Möglichkeiten:
Mit Bash≥4 ist mapfile
es am effizientesten:
mapfile -t my_array < <( my_command )
Ansonsten eine Schleife, die die Ausgabe liest (langsamer, aber sicher):
my_array=()
while IFS= read -r line; do
my_array+=( "$line" )
done < <( my_command )
Wie von Charles Duffy in den Kommentaren vorgeschlagen (danke!), Könnte Folgendes besser sein als die Schleifenmethode in Nummer 2:
IFS=$'\n' read -r -d '' -a my_array < <( my_command && printf '\0' )
Bitte stellen Sie sicher, dass Sie genau dieses Formular verwenden, dh stellen Sie sicher, dass Sie Folgendes haben:
IFS=$'\n'
in derselben Zeile wie die read
Anweisung: Dadurch wird nur die Umgebungsvariable IFS
für die read
Anweisung festgelegt. Es wirkt sich also überhaupt nicht auf den Rest Ihres Skripts aus. Der Zweck dieser Variablen besteht darin, anzugeben read
, dass der Stream beim EOL-Zeichen unterbrochen werden soll \n
.
-r
: das ist wichtig. Es wird empfohlen read
, die Backslashes nicht als Escape-Sequenzen zu interpretieren.
-d ''
: Bitte beachten Sie den Abstand zwischen der -d
Option und ihrem Argument ''
. Wenn Sie hier kein Leerzeichen lassen, ''
wird das nie angezeigt, da es im Schritt zum Entfernen von Anführungszeichen verschwindet, wenn Bash die Anweisung analysiert. Dies weist read
an, das Lesen beim Null-Byte zu beenden. Einige Leute schreiben es als -d $'\0'
, aber es ist nicht wirklich notwendig. -d ''
ist besser.
-a my_array
weist read
an, das Array my_array
beim Lesen des Streams zu füllen.
- Sie müssen die
printf '\0'
Anweisung nach verwenden my_command
, damit read
zurückgegeben wird 0
. Es ist eigentlich keine große Sache, wenn Sie dies nicht tun (Sie erhalten nur einen Rückkehrcode 1
, der in Ordnung ist, wenn Sie ihn nicht verwenden set -e
- was Sie sowieso nicht sollten), aber denken Sie daran. Es ist sauberer und semantisch korrekter. Beachten Sie, dass dies anders ist als das printf ''
, was nichts ausgibt. printf '\0'
Gibt ein Null-Byte aus, das benötigt wird read
, um dort glücklich mit dem Lesen aufzuhören (erinnern Sie sich an die -d ''
Option?).
Wenn Sie können, dh wenn Sie sicher sind, dass Ihr Code auf Bash≥4 ausgeführt wird, verwenden Sie die erste Methode. Und Sie können sehen, dass es auch kürzer ist.
Wenn Sie verwenden möchten, read
hat die Schleife (Methode 2) möglicherweise einen Vorteil gegenüber Methode 3, wenn Sie beim Lesen der Zeilen eine Verarbeitung durchführen möchten: Sie haben direkten Zugriff darauf (über die $line
Variable in dem von mir angegebenen Beispiel) und Sie haben auch Zugriff auf die bereits gelesenen Zeilen (über das Array ${my_array[@]}
in dem Beispiel, das ich gegeben habe).
Beachten Sie, dass dies mapfile
eine Möglichkeit bietet, einen Rückruf für jede gelesene Zeile auswerten zu lassen. Sie können sogar festlegen, dass dieser Rückruf nur alle N gelesenen Zeilen aufgerufen wird . Schauen Sie sich help mapfile
die Optionen -C
und die -c
darin enthaltenen an. (Meine Meinung dazu ist, dass es ein bisschen klobig ist, aber manchmal verwendet werden kann, wenn Sie nur einfache Dinge zu tun haben - ich verstehe nicht wirklich, warum dies überhaupt implementiert wurde!).
Jetzt werde ich Ihnen erklären, warum die folgende Methode:
my_array=( $( my_command) )
ist kaputt, wenn Leerzeichen vorhanden sind:
$ # I'm using this command to test:
$ echo "one two"; echo "three four"
one two
three four
$ # Now I'm going to use the broken method:
$ my_array=( $( echo "one two"; echo "three four" ) )
$ declare -p my_array
declare -a my_array='([0]="one" [1]="two" [2]="three" [3]="four")'
$ # As you can see, the fields are not the lines
$
$ # Now look at the correct method:
$ mapfile -t my_array < <(echo "one two"; echo "three four")
$ declare -p my_array
declare -a my_array='([0]="one two" [1]="three four")'
$ # Good!
Dann empfehlen einige Leute, das Problem IFS=$'\n'
zu beheben:
$ IFS=$'\n'
$ my_array=( $(echo "one two"; echo "three four") )
$ declare -p my_array
declare -a my_array='([0]="one two" [1]="three four")'
$ # It works!
Aber jetzt verwenden wir einen anderen Befehl mit Globs :
$ echo "* one two"; echo "[three four]"
* one two
[three four]
$ IFS=$'\n'
$ my_array=( $(echo "* one two"; echo "[three four]") )
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="t")'
$ # What?
Das liegt daran, dass ich eine Datei t
im aktuellen Verzeichnis habe… und dieser Dateiname mit dem Glob übereinstimmt [three four]
… an dieser Stelle würden einige Leute empfehlen set -f
, Globbing zu deaktivieren. Aber sehen Sie es sich an: Sie müssen es ändern IFS
und verwenden set -f
, um a zu reparieren kaputte Technik (und Sie reparieren sie nicht einmal wirklich)! Dabei kämpfen wir wirklich gegen die Shell und arbeiten nicht mit der Shell .
$ mapfile -t my_array < <( echo "* one two"; echo "[three four]")
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="[three four]")'
hier arbeiten wir mit der Shell!