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

Reading output of a command into an array in Bash

提问人:barp 提问时间:7/11/2012 最后编辑:Benjamin W.barp 更新时间:9/13/2023 访问量:219413

问:

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

ps aux | grep | grep | x 

它一行一行地给出输出,如下所示:

10
20
30

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

数组 bash

评论

5赞 James 3/25/2014
嘿,@barp,回答你的问题,以免你的类型对整个社区造成消耗。
11赞 DDPWNAGE 10/4/2015
@James问题不在于他没有回答他的问题......这是一个问答网站。他只是没有将它们标记为已回答。他应该标记它们。提示。@巴普
5赞 smonff 3/23/2016
请@barp,将问题标记为已回答。
0赞 codeforester 7/26/2018
相关:在 Bash 中循环遍历文件的内容,因为通过进程替换读取命令的输出类似于从文件中读取。

答:

111赞 Michael Schlottke-Lakemper 7/11/2012 #1

你可以使用

my_array=( $(<command>) )

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

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

my_array_length=${#my_array[@]}

现在长度存储在 中。my_array_length

处理扩展和空间问题

为了避免扩展问题,您可以按如下方式设置 env varIFS

  • 正如@smac89在评论和其他答案中所建议的那样
IFS=$'\n' my_array=( $(<command>) )

评论

26赞 ikwyl6 6/30/2016
如果 $(command) 的输出有空格和多行带空格怎么办?我添加了“$(command)”,它将所有行的所有输出放入数组的第一个 [0] 元素中。
3赞 Vito 1/17/2017
@ikwyl6解决方法是将命令输出分配给变量,然后用它创建一个数组或将其添加到数组中。 然后或VAR="$(<command>)"my_array=("$VAR")my_array+=("$VAR")
0赞 rautamiekka 1/23/2021
@Vito不需要双引号,因为从命令替换输出组成的变量已经隐式地对输出进行了双引号,从而更容易在 .VAR="$(COMMAND)"COMMAND
3赞 smac89 3/25/2021
@ikwyl6 如果输入包含换行符,则按照其他答案的建议进行更改以反映这一点。.这对我有用IFSIFS=$'\n' my_array=( $(<command>) )
1赞 Charles Duffy 4/3/2021
如果您有一行只包含一个 ?您将获得数组中当前目录中的文件名列表。*
23赞 Youness 9/26/2015 #2

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

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

或者,可以通过添加以下脚本来循环访问此数组:

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

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

评论

4赞 Hubert Grzeskowiak 6/7/2018
由于上述答案中提到的原因,这是一个可怕的主意
2赞 Lou 5/13/2021
@HubertGrzeskowiak“上面”和“下面”在 SO 中没有意义。你指的是谁的答案?
2赞 Hubert Grzeskowiak 5/13/2021
好点@Lou。我的意思是 stackoverflow.com/a/32931403/2445864
234赞 gniourf_gniourf 10/4/2015 #3

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

要获取数组中命令的输出,每个元素一行,基本上有 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 语句在同一行:这只会为 READ 语句设置环境变量。因此,它完全不会影响脚本的其余部分。这个变量的目的是告诉在 EOL 字符处中断流。IFSread\n
    • -r:这很重要。它告诉不要将反斜杠解释为转义序列。read
    • -d '':请注意选项与其参数之间的空格。如果你不在这里留空格,则永远不会看到 ,因为当 Bash 解析语句时,它将在引号删除步骤中消失。这告诉停止读取零字节。有些人把它写成 ,但实际上没有必要。 更好。-d''''read-d $'\0'-d ''
    • -a my_array告知在读取流时填充数组。readmy_array
    • 您必须在 之后使用语句,以便返回 ;如果你不这样做,这实际上没什么大不了的(你只会得到一个返回代码,如果你不使用也没关系——无论如何你都不应该这样做),但请记住这一点。它更干净,语义上更正确。请注意,这与不输出任何内容的 不同。 打印一个空字节,需要它才能愉快地停止读取那里(还记得这个选项吗?printf '\0'my_commandread01set -eprintf ''printf '\0'read-d ''

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

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

请注意,它提供了一种在读取每行时评估回调的方法,实际上您甚至可以告诉它每读取 N 行才调用此回调;看看其中的选项和选项。(我对此的看法是,它有点笨拙,但如果你只有简单的事情要做,有时可以使用——我真的不明白为什么一开始就实现了它!mapfilehelp 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?

那是因为我在当前目录中调用了一个文件......并且此文件名与 glob 匹配......在这一点上,有些人会建议使用 来禁用通配:但看看它:你必须改变和使用才能修复一个损坏的技术(你甚至没有真正修复它)!当这样做时,我们实际上是在与壳体作斗争,而不是与壳体合作t[three four]set -fIFSset -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]")'

在这里,我们正在使用 Shell!

评论

5赞 Gene Pavlovsky 4/20/2016
这太棒了,我以前从未听说过,这正是我多年来一直缺少的东西。我想最新版本有很多不错的新功能,我应该花几天时间阅读文档并写下一份不错的备忘单。mapfilebash
9赞 Gene Pavlovsky 4/20/2016
顺便说一句,要在 shell 脚本中使用此语法,shebang 行应该是 - 如果运行为 ,bash 将退出并出现语法错误。< <(command)#!/bin/bash#!/bin/sh
2赞 gniourf_gniourf 1/16/2017
@Vito:确实,这个答案仅适用于 Bash,但这应该不是问题,因为严格兼容的 POSIX shell 甚至没有实现数组(并且根本不了解数组,当然,位置参数数组除外)。shdash$@
4赞 Charles Duffy 3/19/2019
作为另一个不需要 bash 4.0 的替代方案,请考虑 -- 它既可以在 bash 3.x 中正常工作,也可以通过从 到 的失败退出状态。IFS=$'\n' read -r -d '' -a my_array < <(my_command && printf '\0')my_commandread
2赞 CodingInCircles 11/15/2019
无论出于何种原因,从终端运行它都可以使用 ,但从 cron 作业运行,仅使用 works 调用。当我从 cron 作业中时,它说:意外令牌“<”附近的语法错误。知道为什么吗?sourcebashsource
-1赞 Usman Ali Maan 1/27/2022 #4

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

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