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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27993448/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-18 12:11:56  来源:igfitidea点击:

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

jsonlinuxbashshelljq

提问by odieatla

I'm using jqto parse a JSON file, extracting each JSON array in a series into a shell array.

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

My current code looks like the following:

我当前的代码如下所示:

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

error message:

错误信息:

error: i is not defined

I also replaced

我也换了

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

with

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

still not working. Any idea? Any help is appreciated!

还是行不通。任何的想法?任何帮助表示赞赏!



Edit: Sample Input Data

编辑:示例输入数据

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

回答by Charles Duffy

There's a fair bit of room for improvement. Let's start here:

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

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

...first, you don't actually need to use cat; it's slowing your performance, because it forces jqto read from a pipe rather than from your input file directly. Just running jq <"$INPUT"would be more robust (or, better, <"$input", to avoid using all-uppercase names, which are reserved by convention for shell builtins and environment variables).

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

Second, you need to quote all variable expansions, including the expansion of the input file's name -- otherwise, you'll get bugs whenever your filename contains spaces.

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

Third, array=( $(stuff) )splits the output of stuffon all characters in IFS, and expands the results of that splitting as a series of glob expressions (so if the output contains *.txt, and you're running this script in a directory that contains text files, you get the names of those files in your result array). Splitting on newlines only would mean you could correctly parse multi-word strings, and disabling glob expansion is necessary before you can use this technique reliably in the presence of glob characters. One way to do this is to set IFS=$'\n'and run set -hbefore running this command; another is to redirect the output of your command into a while readloop (shown below).

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

Fourth, string substitution into code is bad practice in any language -- that way lies (local equivalents to) Bobby Tables, allowing someone who's supposed to be able to only change the data passed into your process to provide content which is processed as executable code (albeit, in this case, as a jqscript, which is less dangerous than arbitrary code execution in a more full-featured language; still, this can allow extra data to be added to the output).

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

Next, once you're getting jqto emit newline-separated content, you don't need to read it into an array at all: You can iterate over the content as it's written from jqand read into your shell, thus preventing the shell from needing to allocate memory to buffer that content:

接下来,一旦您开始jq发出以换行符分隔的内容,您就根本不需要将其读入数组:您可以在从jqshell写入和读取内容时对其进行迭代,从而防止 shell 需要分配内存来缓冲该内容:

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

Finally -- let's say you dowant to work with an array. There are two ways to do this that avoid pitfalls. One is to set IFSexplicitly and disable glob expansion before the assignment:

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

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

The other is to assign to your array with a loop:

另一种是使用循环分配给您的数组:

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

...or, as suggested by @JohnKugelman, to use read -ato read the whole array in one operation:

...或者,正如@JohnKugelman 所建议的,用于read -a在一个操作中读取整个数组:

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

回答by John Kugelman

Variables aren't interpolated inside single quotes. Use double quotes instead, and remove the existing quotes.

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

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

Or use the --argoption and then you can stick with single quotes.

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

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

You could also fix the useless use of cat:

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

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

Also, see @CharlesDuffy's answer for a great, detailed explanation of why assigning to array like this is unsafe.

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

回答by Craig

If you have already stored the result of some JSON into a variable called $MY_VAR:

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

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

It took me WAY too long to figure this out. All the examples I've seen were convoluted, and I had to piece this together.

我花了太长时间才弄清楚这一点。我见过的所有例子都很复杂,我不得不把它们拼凑起来。

回答by tripleee

jqis capable of extracting the structure in one go, so the entire loop is superfluous. If the input JSON contains more records than you have values in nvars, use the index to chop.

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

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