【问题标题】:Assigning space-separated computed values to array in bash is inconsistent with printing将空格分隔的计算值分配给bash中的数组与打印不一致
【发布时间】:2022-08-13 16:17:29
【问题描述】:

我有以下纯文本 az cli 输出:

echo $raw_containers_string
[
  {
    \"name\": \"123\"
  },
  {
    \"name\": \"vbm-container\"
  }
]

经过一些文本优化后,我返回了一个包含此(zsh)的字符串:

echo $raw_containers_string | grep name | cut -d \":\" -f2 | tr \'\\n\' \" \"
\"123\"  \"vbm-container\" %

(它的末尾还有一个 % 符号,但那是expected

我现在需要创建一个包含这 2 个字符串(123 和 vbm-container)的数组来遍历它。

  1. declare -a arr=($(echo $raw_containers_string | grep name | cut -d \":\" -f2 | tr \'\\n\' \" \"))
    • 返回\"123\" \"vb -co t i r\"
  2. arr=($(echo $raw_containers_string | grep name | cut -d \":\" -f2 | tr \'\\n\' \" \"))
    • 返回\"123\" \"vb -co t i r\"

    这些是数组的索引(如果重要的话):

    ➜  bash-az-list-blobs git:(master) ✗ echo $myvar[0]
    
    ➜  bash-az-list-blobs git:(master) ✗ echo $myvar[1]
     \"123\"  \"vb
    ➜  bash-az-list-blobs git:(master) ✗ echo $myvar[2]
    -co
    ➜  bash-az-list-blobs git:(master) ✗ echo $myvar[3]
    t
    ➜  bash-az-list-blobs git:(master) ✗ echo $myvar[4]
    i
    ➜  bash-az-list-blobs git:(master) ✗ echo $myvar[5]
    
    ➜  bash-az-list-blobs git:(master) ✗ echo $myvar[6]
    r\"
    

    问题

    1. 我想了解为什么将其打印到 终端并将其分配给变量是不同的。
    2. 我也会 想知道我如何将我的精炼分配给 zsh 中的一个数组,以便 echo arr 返回一个 2 元素可迭代数组。
  • 如果您使用的是 zsh,为什么这会被标记为 bash?

标签: arrays bash text


【解决方案1】:

您指望不带引号的变量/命令替换的分词来分隔数组的元素,并且显然也更改了IFS。这些都是坏主意,它们加在一起是混乱的根源。您还使用 zsh 进行交互式测试,它的分词与 bash 非常不同,所以您会得到不同的奇怪的结果。

首先,追踪IFS 的变化并修复它。您似乎将其设置为非常奇怪的东西,至少包含“m”、“n”、“a”和“e”,因此它可能设置错误。

如果它确实需要改变,你需要阻止它逗留改变了。您可以通过将其设置为命令的前缀(例如IFS=',' read field1 field2)或在使用更改后的值(例如saveIFS="$IFS"; IFS=","; doSomethingWithIFS; IFS="$saveIFS"), or just find a different way to solve whatever you changed IFS`)后重置它来对特定命令进行本地更改。

第二,养成double-quoting variable and command substitutions的习惯。在某些特定情况下,您不应该对它们进行双引号,而在某些情况下它是可选的,但最安全的做法是假设在大多数情况下应该双引号。 shellcheck.net 擅长指出引用问题(以及许多其他常见错误)。

不要在不带引号的替换上使用分词来填充数组,而是使用readarray -t 将输入的每一行读入一个单独的数组元素(或read -ra 来读取进入元素 - 但首先修复IFS,否则它也会奇怪地分裂)。请注意,您不能将它们放在管道中,或者它们在子shell中执行并且值会丢失;相反,使用进程替换:readarray -t < <(somecommand)

第三,grep | cut 将输出带有额外空格和引号的实际值。无论如何,在解析 JSON 时,最好使用像 jq 这样真正会说该语言的工具。

所以,所有这些都解决了,试试这个:

readarray -t arr < <(echo "$raw_containers_string" | jq -r '.[].name')

Léa Gris 指出,您可以通过使用 --argjson 而不是 echo 和管道将字符串传递给 jq 来进一步简化此过程:

readarray -t arr < <(jq -rn --argjson j "$raw_containers_string" '$j[].name')

(如果您需要检查数组的内容——甚至是普通变量——declare -p arrecho 更可靠。)

【讨论】:

  • 保存 echo 和子壳管道:readarray -t arr &lt; &lt;(jq -rn --argjson j "$raw_containers_string" '$j[].name')
  • @LéaGris 不错。我已经在里面编辑了...
  • 它不能完全替代将 JSON 作为流进行管道传输。由于参数长度限制,JSON 参数可能不适合。也就是说,您通常会通过某些命令获得 JSON,并花费一个子 shell 来收集 JSON 字符串,然后另一个子 shell 将其流式传输到 jq 有点浪费。将提供 JSON 的命令的输出直接通过管道传输到jq,并让系统处理兑现(它比在 shell 中重新发明缓存更好。
【解决方案2】:

使用jq

eval "names=($(echo "$raw_containers_string" |
               jq -r '.[].name|@sh'))"

现在你有一个 shell 数组 names,其中每个名称都是一个元素。管道到@sh 意味着每个值都被引用为一个单独的shell 字,对eval 是安全的。

【讨论】:

    猜你喜欢
    • 2018-02-19
    • 2013-11-07
    • 1970-01-01
    • 1970-01-01
    • 2012-03-06
    • 2023-03-15
    • 2015-05-26
    • 2018-01-04
    • 1970-01-01
    相关资源
    最近更新 更多