137

我需要将脚本中命令的输出读入一个数组。该命令例如:

ps aux | grep | grep | x 

它逐行给出输出,如下所示:

10
20
30

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

4

5 回答 5

205

如果命令的输出包含空格(这是相当频繁的)或全局字符,如*, ?, ,则其他答案将中断[...]

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

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

    mapfile -t my_array < <( my_command )
    
  2. 否则,循环读取输出(较慢,但安全):

    my_array=()
    while IFS= read -r line; do
        my_array+=( "$line" )
    done < <( my_command )
    
  3. 正如 Charles Duffy 在评论中所建议的(谢谢!),以下方法可能比第 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 解析语句时'',它将在引号删除步骤中消失。这告诉read在零字节处停止读取。有些人将其写为-d $'\0',但这并不是必需的。-d ''更好。
    • -a my_array告诉在读取流时read填充数组。my_array
    • 必须使用afterprintf '\0'语句,以便返回; 如果你不使用它实际上没什么大不了的(你只会得到一个返回码,如果你不使用也没关系——无论如何你都不应该使用它),但请记住这一点。它更干净,语义更正确。请注意,这与不输出任何内容的 不同。打印一个空字节,愉快地停止在那里阅读(还记得这个选项吗?)。 my_commandread01set -eprintf ''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!

但是现在让我们使用另一个命令,带有glob

$ 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来禁用通配符:但请看一下:您必须更改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]")'

在这里,我们正在使用外壳!

于 2015-10-04T08:13:35.367 回答
102

您可以使用

my_array=( $(<command>) )

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

您可以使用访问该数组的长度

my_array_length=${#my_array[@]}

现在长度存储在my_array_length.

于 2012-07-11T06:32:11.760 回答
20

这是一个简单的例子。想象一下,您要将文件和目录名称(在当前文件夹下)放入一个数组并计算它们。脚本会是这样的;

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

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

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

请注意,这是核心概念,输入必须在处理之前进行清理,即删除多余的字符,处理空字符串等(这超出了本线程的主题)。

于 2015-09-25T22:34:43.797 回答
0

假设您想将整个目录列表复制到当前目录到数组中,它一直对我有帮助

bucketlist=($(ls))
#then print them one by one
for bucket in "${bucketlist[@]}"; do
echo " here is bucket: ${bucket}"
done
于 2022-01-27T09:44:13.320 回答
-2

这将创建一个包含所有 .js 文件的数组

output=$(find ./src/components/ -type f -name "*.js" -print)


#loop output_array
for i in "${output[@]}"
do
  printf "$i"
done
于 2021-11-02T11:04:30.487 回答