在 Bash 中将命令的输出读入数组

我需要将脚本中命令的输出读入数组,例如:

ps aux | grep | grep | x

它给出的输出行是这样的:

10
20
30

我需要将命令输出中的值读入一个数组,然后如果数组的大小小于3,我将做一些工作。

199282 次浏览

你可以用

my_array=( $(<command>) )

将命令 <command>的输出存储到数组 my_array中。

您可以使用

my_array_length=${#my_array[@]}

现在长度存储在 my_array_length中。

这里有一个简单的例子。假设您要将文件和目录名(在当前文件夹下)放到一个数组中并对它们进行计数。剧本是这样的:

my_array=( `ls` )
my_array_length=${#my_array[@]}
echo $my_array_length

或者,您可以通过添加以下脚本来迭代此数组:

for element in "${my_array[@]}"
do
echo "${element}"
done

请注意,这是核心概念和 在处理之前,输入必须 < strong > 消毒 ,即删除额外的字符、处理空字符串等(这超出了本线程的主题)。

如果命令的输出包含空格(这种情况比较常见)或者像 *?[...]这样的通配符,那么其他的答案就会中断。

要获得数组中每个元素一行的命令输出,基本上有3种方法:

  1. Bash ≥4时使用 mapfileーー这是最有效的:

    mapfile -t my_array < <( my_command )
    
  2. Otherwise, a loop reading the output (slower, but safe):

    my_array=()
    while IFS= read -r line; do
    my_array+=( "$line" )
    done < <( my_command )
    
  3. As suggested by Charles Duffy in the comments (thanks!), the following might perform better than the loop method in number 2:

    IFS=$'\n' read -r -d '' -a my_array < <( my_command && printf '\0' )
    

    请确保您使用的正是此表格,即,请确保您有以下内容:

    • 这只会设置环境变量 IFS只适用于 read语句。,所以它不会影响你的脚本的其余部分。这个变量的目的是告诉 read在 EOL 字符 \n处中断流。
    • 这很重要,它告诉 read 不将反斜杠解释为转义序列。
    • 请注意 -d选项和它的参数 ''之间的空格。如果您在这里没有留下空格,就永远不会看到 '',因为当 Bash 解析语句时,它将在 移除引文步骤中消失。这告诉 read在空字节处停止读取。有些人把它写成 -d $'\0',但实际上没有这个必要。-d ''更好。
    • -a my_array告诉 read在读取流时填充数组 my_array
    • 你必须使用 printf '\0'语句 my_command0 my_command,这样 read才能返回 0; 如果你不这样做,这实际上没什么大不了的(你只会得到一个返回码 1,如果你不使用 set -e也没关系——反正你也不应该使用 set -e) ,但是请记住这一点。更干净,语义更正确。注意,这与 printf ''不同,printf ''不输出任何东西。printf '\0'输出一个空字节,read需要这个空字节才能停止读取(还记得 -d ''选项吗?).

如果可以,也就是说,如果确定代码将在 Bash ≥4上运行,请使用第一个方法。你可以看到它也变短了。

如果你想使用 read,循环(方法2)可能比方法3有优势,如果你想在读取行时进行一些处理: 你可以直接访问它(通过我给出的例子中的 $line变量) ,你也可以访问已经读取的行(通过我给出的例子中的数组 ${my_array[@]})。

请注意,mapfile提供了一种在每一行读取时进行回调 eval’d 的方法,实际上,您甚至可以告诉它在每一行读取时只调用这个回调; 请看一下 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来禁用 globbing,但是看看它: 你必须改变 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]")'

我们在这里与壳工作!

假设你想把整个目录列表复制到一个数组的工作目录中,它总是能帮到我

bucketlist=($(ls))
#then print them one by one
for bucket in "${bucketlist[@]}"; do
echo " here is bucket: ${bucket}"
done