如果命令的输出包含空格(相当频繁)或像*、?、[...] 这样的全局字符,其他答案将中断。
要在数组中获取命令的输出,每个元素一行,基本上有 3 种方法:
-
Bash≥4 使用mapfile——这是最有效的:
mapfile -t my_array < <( my_command )
-
否则,循环读取输出(较慢,但安全):
my_array=()
while IFS= read -r line; do
my_array+=( "$line" )
done < <( my_command )
-
正如 Charles Duffy 在 cmets 中所建议的那样(谢谢!),以下方法可能比第 2 项中的循环方法执行得更好:
IFS=$'\n' read -r -d '' -a my_array < <( my_command && printf '\0' )
请确保您完全使用此表单,即确保您具有以下内容:
-
IFS=$'\n' 与read 语句在同一行: 这只会设置环境变量IFS 仅用于read 语句。 所以它不会'完全不会影响脚本的其余部分。此变量的目的是告诉read 在 EOL 字符\n 处中断流。
-
-r:这很重要。它告诉read 不要将反斜杠解释为转义序列。
-
-d '':请注意-d 选项与其参数'' 之间的空格。如果你不在这里留下空格,'' 将永远不会被看到,因为它会在 Bash 解析语句时在 quote remove 步骤中消失。这告诉read 在零字节处停止读取。有些人写成-d $'\0',其实没必要。 -d '' 更好。
-
-a my_array 告诉 read 在读取流时填充数组 my_array。
- 您必须在
my_command 之后使用printf '\0' 语句,以便read 返回0;如果你不这样做,这实际上没什么大不了的(你只会得到一个返回码1,如果你不使用set -e 也没关系——无论如何你都不应该这样做),但只要忍受头脑。它更干净,语义更正确。请注意,这与printf '' 不同,后者不输出任何内容。 printf '\0' 打印一个空字节,read 需要它来愉快地停止阅读(还记得 -d '' 选项吗?)。
如果可以,即如果您确定您的代码将在 Bash≥4 上运行,请使用第一种方法。而且你可以看到它也更短了。
如果您想使用read,如果您想在读取行时进行一些处理,则循环(方法 2)可能比方法 3 具有优势:您可以直接访问它(通过 $line我给出的示例中的变量),并且您还可以访问已经读取的行(通过我给出的示例中的数组${my_array[@]})。
请注意,mapfile 提供了一种在读取的每一行上进行回调评估的方法,实际上您甚至可以告诉它仅在每读取 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!
但是现在让我们使用另一个命令,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?
那是因为我在当前目录中有一个名为 t 的文件……并且此文件名与 glob [three four] 匹配……此时有些人会建议使用 set -f 禁用globbing:但请看一下:您必须更改 IFS 并使用 set -f 才能修复损坏的技术(而且您甚至没有真正修复它)!这样做时,我们实际上是在与 shell 作斗争,而不是与 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]")'
我们正在使用 shell!