bash:遍历按索引选择的 JSON 数组的成员

bash: Iterating over members of a JSON array selected by index

我正在使用 jq 解析 JSON 文件,将系列中的每个 JSON 数组提取到 shell 数组中。

我当前的代码如下所示:

for ((i = 0; i < ${#nvars[@]}; i++)); do
    v1=($(cat $INPUT | jq '."config"[i]."var1"[]'))
    echo $v1
done

错误信息:

error: i is not defined

我也换了

v1=($(cat $INPUT | jq '."config"[i]."var1"[]'))

v1=($(cat $INPUT | jq '."config"[$i]."var1"[]'))

仍然无法正常工作。任何想法?任何帮助表示赞赏!


编辑:示例输入数据

{
    "config-vars":[
        {
            "var1":["v1","v2"],
            "var2":""
        },
        {
            "var1":["v3",""],
            "var2":"v4"
        }
    ]
}

变量不会插入单引号内。请改用双引号,并删除现有的引号。

v1=($(cat $INPUT | jq ".config[$i].var1[]"))

或使用 --arg 选项,然后您可以坚持使用单引号。

v1=($(cat $INPUT | jq --arg i "$i" '.config[$i].var1[]'))

您还可以修复 cat 的无用使用:

v1=($(jq ".config[$i].var1[]" "$INPUT"))

此外,请参阅@CharlesDuffy 的回答,详细了解为什么像这样分配给数组是不安全的。

还有很大的改进空间。让我们从这里开始:

v1=($(cat $INPUT | jq '."config"[$i]."var1"[]'))

...首先,您实际上不需要使用 cat;它会降低您的性能,因为它会强制 jq 从管道读取而不是直接从您的输入文件读取。只是 运行ning jq <"$INPUT" 会更健壮(或者,更好的是 <"$input",以避免使用全大写的名称,这些名称按约定保留用于 shell 内置函数和环境变量).

其次,您需要引用所有变量扩展,包括输入文件名的扩展——否则,只要您的文件名包含空格,您就会遇到错误。

第三,array=( $(stuff) )IFS 中的所有字符上拆分 stuff 的输出,并将该拆分的结果扩展为一系列 glob 表达式(因此,如果输出包含 *.txt,并且您在包含文本文件的目录中 运行 宁此脚本,您将在结果数组中获得这些文件的名称)。仅在换行符上拆分意味着您可以正确解析多词字符串,并且在存在 glob 字符的情况下可靠地使用此技术之前,必须禁用 glob 扩展。一种方法是在 运行 执行此命令之前设置 IFS=$'\n' 和 运行 set -h;另一种方法是将命令的输出重定向到 while read 循环(如下所示)。

第四,将字符串替换为代码在任何语言中都是不好的做法——这种方式存在(本地等同于)Bobby Tables,允许应该只能将传递到您的进程的数据更改为提供被处理为可执行代码的内容(尽管在这种情况下,作为 jq 脚本,这比以更全功能的语言执行任意代码更危险;不过,这可以允许添加额外的数据到输出)。

接下来,一旦您让 jq 发出以换行符分隔的内容,您根本不需要将其读入数组:您可以迭代从 [= 写入的内容16=] 并读入您的 shell,从而防止 shell 需要分配内存来缓冲该内容:

while IFS= read -r; do
  echo "read content from jq: $REPLY"
done < <(jq -r --arg i "$i" '.config[$i | tonumber].var1[]' <"$input")

最后 -- 假设您 想要使用数组。有两种方法可以避免陷阱。一种是显式设置 IFS 并在赋值前禁用 glob 扩展:

IFS=$'\n' # split only on newlines
set -f
result=( $(jq -r ... <"$input") )

另一种是用循环赋值给你的数组:

result=( )
while IFS= read -r; do
  result+=( "$REPLY" )
done < <(jq -r ... <"$input")

...或者,按照@JohnKugelman 的建议,使用 read -a 在一次操作中读取整个数组:

IFS=$'\n' read -r -d '' -a result < <(jq -r ... <"$input")

jq可以一次提取结构,所以整个循环是多余的。如果输入 JSON 包含的记录多于您在 nvars 中的值,请使用索引进行分割。

jq -r '."config-vars"[]."var1"' "$INPUT" |
head -n "${#nvars[@]}"  # If you need just the #nvars first values

如果您已经将某些 JSON 的结果存储到名为 $MY_VAR 的变量中:

while IFS= read -r; do
  echo "$REPLY"
done < <(echo "$MY_VAR" | jq -r '.[]')