Pozostałe odpowiedzi pęknie jeśli wyjście komendy zawiera spacje (co jest dość częste) lub znaki jak glob *
, ?
, [...]
.
Aby uzyskać wynik polecenia w tablicy, z jednym wierszem na element, istnieją zasadniczo 3 sposoby:
Z Bash≥4 użyj mapfile
- jest najbardziej wydajny:
mapfile -t my_array < <( my_command )
W przeciwnym razie pętla odczytująca wyjście (wolniej, ale bezpiecznie):
my_array=()
while IFS= read -r line; do
my_array+=( "$line" )
done < <( my_command )
Jak zasugerował Charles Duffy w komentarzach (dzięki!), Poniższe mogą działać lepiej niż metoda pętli w numerze 2:
IFS=$'\n' read -r -d '' -a my_array < <( my_command && printf '\0' )
Upewnij się, że używasz dokładnie tego formularza, tj. Upewnij się, że masz następujące:
IFS=$'\n'
w tym samym wierszu co read
instrukcja: spowoduje to ustawienie zmiennej środowiskowej tylko IFS
dla read
instrukcji. Więc w ogóle nie wpłynie to na resztę twojego skryptu. Celem tej zmiennej jest nakazanie read
przerwania strumienia przy znaku EOL \n
.
-r
: To jest ważne. Mówi, read
aby nie interpretować odwrotnych ukośników jako sekwencji ucieczki.
-d ''
: zwróć uwagę na spację między -d
opcją a jej argumentem ''
. Jeśli nie zostawisz tutaj spacji, ''
nigdy nie będzie widoczne, ponieważ zniknie w kroku usuwania cytatu, gdy Bash przeanalizuje instrukcję. To mówi, read
aby przestać czytać na bajcie zerowym. Niektórzy piszą to jako -d $'\0'
, ale tak naprawdę nie jest to konieczne. -d ''
jest lepiej.
-a my_array
mówi, read
aby wypełnić tablicęmy_array
podczas czytania strumienia.
- Musisz użyć
printf '\0'
instrukcji później my_command
, aby to read
powróciło 0
; to właściwie nie jest wielka sprawa, jeśli tego nie zrobisz (otrzymasz po prostu kod powrotu 1
, co jest w porządku, jeśli nie używasz set -e
- czego i tak nie powinieneś), ale po prostu miej to na uwadze. Jest czystszy i bardziej poprawny semantycznie. Zauważ, że różni się to od printf ''
, które nic nie wyświetla. printf '\0'
wypisuje bajt zerowy, potrzebny, read
aby szczęśliwie przestał tam czytać (pamiętasz -d ''
opcję?).
Jeśli możesz, tj. Jeśli masz pewność, że Twój kod będzie działał na Bash≥4, użyj pierwszej metody. Widać też, że jest krótszy.
Jeśli chcesz użyć read
, pętla (metoda 2) może mieć przewagę nad metodą 3, jeśli chcesz wykonać pewne przetwarzanie podczas odczytywania wierszy: masz do niej bezpośredni dostęp (poprzez $line
zmienną z podanego przykładu) i masz również dostęp do już przeczytanych wierszy (poprzez tablicę ${my_array[@]}
z przykładu, który podałem).
Zauważ, że mapfile
umożliwia to wywołanie zwrotne w każdej przeczytanej linii, aw rzeczywistości możesz nawet powiedzieć mu, aby wywoływał to wywołanie zwrotne tylko po przeczytaniu N linii; spójrz na help mapfile
i opcje -C
i -c
tam. (Moja opinia na ten temat jest taka, że jest to trochę niezgrabne, ale czasami może być używane, jeśli masz tylko proste rzeczy do zrobienia - nie bardzo rozumiem, dlaczego zostało to wdrożone!).
Teraz powiem ci, dlaczego następująca metoda:
my_array=( $( my_command) )
jest uszkodzony, gdy są spacje:
$ # 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!
Wtedy niektórzy ludzie zalecą użycie, IFS=$'\n'
aby to naprawić:
$ 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!
Ale teraz użyjmy innego polecenia, z globami :
$ 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?
Dzieje się tak, ponieważ mam plik o nazwie t
w bieżącym katalogu… a ta nazwa pliku jest dopasowana przez glob [three four]
… w tym momencie niektórzy ludzie zalecaliby użycie set -f
do wyłączenia globbingu: ale spójrz na to: musisz zmienić IFS
i użyć, set -f
aby móc naprawić zepsuta technika (a nawet tego nie naprawiasz)! robiąc to, naprawdę walczymy z powłoką, a nie z nią .
$ mapfile -t my_array < <( echo "* one two"; echo "[three four]")
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="[three four]")'
tutaj pracujemy z powłoką!