【问题标题】:Reading output of a command into an array in Bash将命令的输出读入 Bash 中的数组
【发布时间】:2012-07-10 17:01:46
【问题描述】:

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

ps aux | grep | grep | x 

它会像这样逐行给出输出:

10
20
30

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

【问题讨论】:

  • 嘿@barp,回答你的问题,以免你的类型成为整个社区的流失。
  • @James 问题不在于他没有回答他的问题……这是一个问答网站。他只是没有将它们标记为已回答。他应该标记它们。暗示。 @barp
  • 请@barp,将问题标记为已回答。
  • 相关:Looping through the content of a file in Bash 因为通过进程替换读取命令的输出类似于从文件中读取。

标签: arrays bash


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

    output=$(find ./src/components/ -type f -name "*.js" -print)
    
    
    #loop output_array
    for i in "${output[@]}"
    do
      printf "$i"
    done
    

    【讨论】:

    • 不,这将创建单个变量$output
    • yaa 我的意思是我们可以遍历所有 .js 文件
    【解决方案3】:

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

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

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

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

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

    【讨论】:

    • 由于上述答案中提到的原因,这个想法很糟糕
    • @HubertGrzeskowiak "Above" 和 "Below" 在 SO 中没有任何意义。你指的是谁的答案?
    • 好点@Lou。我的意思是stackoverflow.com/a/32931403/2445864
    【解决方案4】:

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

    要在数组中获取命令的输出,每个元素一行,基本上有 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 在 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!

    【讨论】:

    • 这太好了,我以前从未听说过mapfile,这正是我多年来一直想念的。我猜bash 的最新版本有很多不错的新功能,我应该花几天时间阅读文档并写下一份不错的备忘单。
    • 顺便说一句,要在 shell 脚本中使用 &lt; &lt;(command) 这个语法,shebang 行应该是 #!/bin/bash - 如果以 #!/bin/sh 运行,bash 将退出并出现语法错误。
    • 扩展 @GenePavlovsky 的有用说明,脚本还必须使用 bash 命令 bash my_script.sh 而不是 sh 命令 sh my_script.sh 运行
    • @Vito:确实,这个答案只适用于 Bash,但这应该不是问题,因为严格兼容的 POSIX shell 甚至不实现数组(shdash don '根本不知道数组,当然,除了位置参数$@数组)。
    • 作为另一个不需要 bash 4.0 的替代方案,请考虑 IFS=$'\n' read -r -d '' -a my_array &lt; &lt;(my_command &amp;&amp; printf '\0')——它既可以在 bash 3.x 中正常工作,也可以通过从 my_command 到 @ 的失败退出状态987654386@.
    【解决方案5】:

    你可以使用

    my_array=( $(<command>) )
    

    将命令&lt;command&gt;的输出存储到数组my_array中。

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

    my_array_length=${#my_array[@]}
    

    现在长度存储在my_array_length

    【讨论】:

    • 如果 $(command) 的输出有空格和多行有空格怎么办?我添加了“$(command)”,它将所有行的所有输出放入数组的第一个 [0] 元素中。
    • @ikwyl6 一种解决方法是将命令输出分配给一个变量,然后用它制作一个数组或将其添加到一个数组中。 VAR="$(&lt;command&gt;)" 然后my_array=("$VAR")my_array+=("$VAR")
    • @Vito VAR="$(COMMAND)" 不需要双引号,因为从命令替换输出生成的变量已经隐式地双引号输出,从而更容易在 COMMAND 中使用双引号。跨度>
    • @ikwyl6 如果输入包含换行符,则执行其他答案的建议并更改 IFS 以反映这一点。 IFS=$'\n' my_array=( $(&lt;command&gt;) )。这对我有用
    • 如果你有一行只包含*怎么办?您将获得阵列中当前目录中的文件名列表。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-07-16
    • 2014-06-13
    • 1970-01-01
    • 2021-10-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多