将使用jq解析的数组分配给Bash脚本数组

19

我用 jq 解析了一个 json 文件,像这样:

# cat test.json | jq '.logs' | jq '.[]' | jq '._id' | jq -s

它返回一个如下的数组:[34,235,436,546,.....]

我使用bash脚本描述了一个数组:

# declare -a msgIds = ...

这个数组使用 () 而不是 [],所以当我将上面给出的数组传递给这个数组时,它不能正常工作。

([324,32,45..]) 这会引起问题。如果我删除 jq -s,那么只会形成一个仅包含1个成员的数组。

有没有办法解决这个问题?


4
为什么要使用 jq '.logs' | jq '.[]' | jq '._id',而不是直接使用 jq '.logs[]._id' - hobbs
1
我对Bash脚本和jq都很陌生,欢迎提供建议。 - İlker Demirci
5个回答

31

我们可以通过两种方式解决这个问题。它们分别是:

输入字符串:

// test.json
{
    "keys": ["key1","key2","key3"]
}

方法一:

1)使用 jq -r (输出原始字符串,而非 JSON 文本)。

KEYS=$(jq -r '.keys' test.json)
echo $KEYS
# Output: [ "key1", "key2", "key3" ]

2) 使用@sh(将输入字符串转换为一系列以空格分隔的字符串)。它从字符串中删除方括号[]和逗号(,)。

KEYS=$(<test.json jq -r '.keys | @sh')
echo $KEYS
# Output: 'key1' 'key2' 'key3'

3) 使用 tr 命令从字符串输出中移除单引号。 若要删除特定字符,请在 tr 命令中使用 -d 选项。

KEYS=$((<test.json jq -r '.keys | @sh')| tr -d \') 
echo $KEYS
# Output: key1 key2 key3

4) 通过将逗号分隔的字符串放入圆括号()中,我们可以将其转换为数组。它也被称为复合赋值,其中我们使用一些值声明数组。

ARRAYNAME=(value1 value2  .... valueN)
#!/bin/bash
KEYS=($((<test.json jq -r '.keys | @sh') | tr -d \'\"))

echo "Array size: " ${#KEYS[@]}
echo "Array elements: "${KEYS[@]}

# Output: 
# Array size:  3
# Array elements: key1 key2 key3

方法二:

1) 使用jq -r获取字符串输出,然后使用tr删除诸如方括号、双引号和逗号等字符。

#!/bin/bash
KEYS=$(jq -r '.keys' test.json  | tr -d '[],"')
echo $KEYS

# Output: key1 key2 key3

2) 然后我们可以通过将逗号分隔的字符串放置在圆括号()中来将其转换为数组。

#!/bin/bash
KEYS=($(jq -r '.keys' test.json  | tr -d '[]," '))

echo "Array size: " ${#KEYS[@]}
echo "Array elements: "${KEYS[@]}

# Output:
# Array size:  3
# Array elements: key1 key2 key3

1
对于方法1,收到了“@sh命令未找到”的错误提示,但是方法2完美运行!谢谢! - yfpb
2
@yfpb,@sh是jq的语法结构,而不是shell命令;你需要确保它是jq查询的一部分。 - Charles Duffy
请注意,这些方法只在被提取的字符串没有通配符、引用或IFS字符时才能正确工作。如果有这些字符存在,你需要实际解释jq@sh过滤器产生的引号,而不仅仅是删除它们。Bash可以为你完成这个任务,参见下面的链接 - undefined

12
要正确解析可能包含换行符(以及任何其他任意(非NUL)字符)的值,请使用jq的@sh过滤器生成以空格分隔的带引号字符串,并使用Bash的declare -a将带引号的字符串解析为数组元素。(无需预处理)

foo.json:

{"data": ["$0", " \t\n", "*", "\"", ""]}

str=$(jq -r '.data | @sh' foo.json)
declare -a arr="($str)"   # must be quoted like this

declare -p arr
# declare -a arr=([0]="\$0" [1]=$' \t\n' [2]="*" [3]="\"" [4]="")

更新:jq 1.7(2023-09)
从版本1.7开始,jq现在有一个--raw-output0选项,使其能够输出以空字符结尾的字符串,可以像往常一样读入数组中:
mapfile -d '' arr < <(jq --raw-output0 '.data[]' foo.json)
wait "$!"  # use in bash-4.4+ to get exit status of the process substitution

关于NUL字符的注意事项
JSON字符串中可能包含NUL字符,而shell变量则不行。如果您的JSON输入可能包含NUL字符,则可能需要添加一些特殊处理。
使用@sh过滤器时,JSON字符串中的NUL字符将被静默替换为序列\0。请注意,这使得JSON字符串"\\0"和"\u0000"无法区分。
使用--raw-output0选项时,NUL字符将触发错误,并且jq将以退出状态5终止。
读取多个/嵌套数组
@sh过滤器可以与--raw-output0组合使用,可可靠地一次读取多个数组(或单个嵌套数组),因为它将生成一个以NUL分隔的以空格分隔的带引号字符串列表。
json='[[1,2],[3,4]]' i=0
while read -r -d ''; do
    declare -a "arr$((i++))=($REPLY)"
done < <(jq --raw-output0 '.[]|@sh' <<<$json)

for ((n=0; n<i; n++)); { declare -p "arr$n"; }
# declare -a arr0=([0]="1" [1]="2")
# declare -a arr1=([0]="3" [1]="4")

3
这是最正确的答案,因为即使使用 set -e,它也会导致适当的错误处理。 - Fleshgrinder
1
终于得到了一个很棒的答案,非常感谢。 - undefined
1
终于得到了一个很棒的答案,非常感谢。 - To Kra

9
使用jq -r输出一个字符串"raw",不需要JSON格式化,并使用@sh格式化器将结果格式化为可供shell消费的字符串。根据jq文档:

@sh:

输入经过转义,适合在POSIX shell命令行中使用。如果输入是一个数组,则输出将是一系列用空格分隔的字符串。

因此可以这样做:
msgids=($(<test.json jq -r '.logs[]._id | @sh'))

并获得您想要的结果。

谢谢回复,我想要得到这样的结果:(234,23,56),使用@sh格式化器是否可能?目前输出没有逗号分隔。 - İlker Demirci
1
@İlkerDemirci 为什么?逗号分隔符不是将其读入Bash数组的正确语法,应该使用空格分隔符。 - hobbs
您绝对正确,那个完美地运行了。谢谢。 - İlker Demirci

8

从jq FAQ中(https://github.com/stedolan/jq/wiki/FAQ):

: 如何将由jq生成的JSON文本流转换为相应值的bash数组?

答:一种选择是使用mapfile(又名readarray),例如:

mapfile -t array <<< $(jq -c '.[]' input.json)

在其他shell中可能会有一个替代方案,使用while循环内的read -r。下面的bash脚本将数组x填充为JSON文本。关键点是使用-c选项和bash语法while read -r value; do ... done < <(jq .......):

#!/bin/bash
x=()
while read -r value
do
  x+=("$value")
done < <(jq -c '.[]' input.json)

2

++ 为了解决这个问题,我们可以采用非常简单的方法:

++ 由于我不知道您的输入文件,因此我将创建一个名为input.json的文件,并包含如下内容:

input.json:

{
    "keys": ["key1","key2","key3"]
}

++使用jq从上面的input.json文件中获取值:

命令:cat input.json | jq -r'.keys | @sh'

输出:'key1' 'key2' 'key3'

解释:| @sh删除[和]

++为了去掉' ',我们使用tr

命令:cat input.json | jq -r'.keys | @ sh' | tr -d\'

解释:使用tr delete -d来删除'

++要将其存储在bash数组中,我们使用()和``,并打印出来:

命令:

KEYS=(`cat input.json | jq -r '.keys | @sh' | tr -d \'`)

打印数组的所有元素:echo "${KEYS[*]}"


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接