Другие ответы сломается , если вывод команды содержит пробелы (которые довольно часто) или шарик символы , такие как *, ?, [...].
Чтобы получить вывод команды в массиве с одной строкой на элемент, есть три способа:
При использовании Bash≥4 mapfile- это наиболее эффективно:
mapfile -t my_array < <( my_command )
В противном случае цикл чтения вывода (более медленный, но безопасный):
my_array=()
while IFS= read -r line; do
my_array+=( "$line" )
done < <( my_command )
Как предложил Чарльз Даффи в комментариях (спасибо!), Следующий метод может работать лучше, чем метод цикла в номере 2:
IFS=$'\n' read -r -d '' -a my_array < <( my_command && printf '\0' )
Убедитесь, что вы используете именно эту форму, т.е. убедитесь, что у вас есть следующее:
IFS=$'\n' в той же строке, что и readоператор: это установит только переменную среды IFS для readоператора. Так что это никак не повлияет на остальную часть вашего скрипта. Назначение этой переменной - сообщить readо прерывании потока по символу EOL \n.
-r: это важно. Он говорит read не интерпретировать обратную косую черту как escape-последовательности.
-d '': обратите внимание на пробел между -dпараметром и его аргументом ''. Если вы не оставите здесь пробел, ''он никогда не будет виден, поскольку он исчезнет на этапе удаления цитаты, когда Bash проанализирует оператор. Это говорит readо прекращении чтения на нулевом байте. Некоторые люди пишут это как -d $'\0', но на самом деле это не обязательно. -d ''лучше.
-a my_arrayсообщает readзаполнить массив my_arrayпри чтении потока.
- Вы должны использовать
printf '\0'оператор после my_command , чтобы readвернуть 0; на самом деле это не имеет большого значения, если вы этого не сделаете (вы просто получите код возврата 1, что нормально, если вы не используете set -e- что вам все равно не следует), но просто имейте это в виду. Он чище и семантически корректнее. Обратите внимание, что это отличается от printf '', который ничего не выводит. printf '\0'печатает нулевой байт, необходимый readдля благополучного прекращения чтения (помните -d ''опцию?).
Если вы можете, т.е. если вы уверены, что ваш код будет работать на Bash≥4, используйте первый метод. Вы также можете видеть, что он короче.
Если вы хотите использовать read, цикл (метод 2) может иметь преимущество перед методом 3, если вы хотите выполнять некоторую обработку по мере чтения строк: у вас есть прямой доступ к нему (через $lineпеременную в приведенном мной примере) и у вас также есть доступ к уже прочитанным строкам (через массив ${my_array[@]}в приведенном мной примере).
Обратите внимание, что это mapfileдает возможность иметь функцию eval'd обратного вызова при чтении каждой строки, и фактически вы даже можете указать ему вызывать этот обратный вызов только каждые N прочитанных строк; посмотрите help mapfileи варианты -Cи -cтам. (Я считаю, что это немного неуклюже, но иногда его можно использовать, если у вас есть только простые дела - я вообще не понимаю, почему это вообще было реализовано!).
Теперь я расскажу, почему такой метод:
my_array=( $( my_command) )
не работает, когда есть пробелы:
$ # 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!
Тогда некоторые люди порекомендуют IFS=$'\n'исправить это с помощью:
$ 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!
Но теперь давайте воспользуемся другой командой с глобусами :
$ 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?
Это потому, что у меня есть файл, который называется tв текущем каталоге ... и это имя файла совпадает с глобусом [three four] ... на этом этапе некоторые люди рекомендуют использовать set -fдля отключения глобализации: но посмотрите на это: вы должны изменить IFSи использовать, set -fчтобы иметь возможность исправить сломанная техника (а вы ее даже не исправляете)! при этом мы действительно сражаемся с оболочкой, а не с оболочкой .
$ mapfile -t my_array < <( echo "* one two"; echo "[three four]")
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="[three four]")'
здесь мы работаем с оболочкой!