日常编写 bash 脚本处理 json 时,我们大概率会使用 jq 这个工具,此时我们需要学习一些 jq 的语法,下面总结一些使用过程中遇到的一些问题和解决方式。
使用 bash 变量
$ var1="hello"
$ jq --arg var1 $var1 '.res[$var1]' filename.json
可以多次使用--arg inner_var bash_var
的方式。
检查 key 是否存在
$ jq -r '.result | has("keyname")' filename.json
true # or false
检查字段是否存在某些字段
$ jq -r '.result.var | contains("content")' filename.json
true # or false
获取 object 的 key 或者 value
比如有如下的 json :
{
"key1": {
"key11": {
"key111": {
"key1111": "hello"
}
}
},
"key2": {
"key22": {
"key222": {
"key2222": "world"
}
}
}
}
若只想输入两个 value 的值,hello
和world
。 可以用的方式是:
$ jq -r '.[] | .[] | .[] | to_entries | .[].value' xxx.json
hello
world
注意这边的to_entries
是将内容转变成固定格式:
[
{
"key": "key111",
"value": {
"key1111": "hello"
}
}
]
[
{
"key": "key222",
"value": {
"key2222": "world"
}
}
]
之后再统一取指定的 key 下面的内容。
jq 读取 array 内容到 bash 变量
我们经常需要将 json 的数组内容读取到 bash 的数组中。可以有两种方式,主要看 bash 的版本:
bash 4+
$ mapfile -t arr < <(jq -r 'keys[]' xxx.json)
older bash
arr=()
while IFS='' read -r line; do
arr+=("$line")
done < <(jq 'keys[]' xxx.json)
此外还有一种方式用于纯 json 数组的转换,比如:
#[
# 1, 2, 3, 4, 5, 6, 7, 8, 9
#]
$ arr=($(jq -r '. | @sh' xxx.json))
@sh: The input is escaped suitable for use in a command-line for a POSIX shell. If the input is an array, the output will be a series of space-separated strings.
jq 输出多字段内容读取到 bash 变量中
当我们同时需要读取两个并列的 json 字段的内容到脚本中同时处理时,这个过程稍微有点复杂。
jq -r '. | [.name,.value] | @tsv' model_info.json | \
while IFS=$'\t' read -r name ctx; do
echo $name, $ctx
done
@tsv: The input must be an array, and it is rendered as TSV (tab-separated values). Each input array will be printed as a single line. Fields are separated by a single tab (ascii 0x09). Input characters line-feed (ascii 0x0a), carriage-return (ascii 0x0d), tab (ascii 0x09) and backslash (ascii 0x5c) will be output as escape sequences \n, \r, \t, \ respectively.
总体的思路就是先转成 tsv 格式,后续在 read 指定分隔符读取。
join 指定字段内容
join 是使用指定分隔符去合并相应的数组内容,比如有数组:[1,2,3,4,5]
$ jq -r '.|join("|")' arr.json
1|2|3|4|5
create JSON from associative array
这种情况下我们可以使用 reduce 进行创建,具体操作如下:
#!/bin/bash
# Requires bash with associative arrays
declare -A dict
dict["foo"]=1
dict["bar"]=2
dict["baz"]=3
for i in "${!dict[@]}"
do
echo "$i"
echo "${dict[$i]}"
done |
jq -n -R 'reduce inputs as $i ({}; . + { ($i): (input|(tonumber? // .)) })'
可以看到 reduce 可以将输入的内容切分成 key 和 value,然后按照 key 创建一个新的 json 对象。这边涉及到几个 jq 的语法:
?
是一个错误规避或者是可选的的操作//
表示 Alternative operator, 可选操作符
|(tonumber? // .)
表示将 value 转换成数字,如果 value 转换失败了,则使用原来的 value。
一个比较完整的且安全的函数,可以有如下的封装:
assoc2json() {
declare -n v=$1
printf '%s\0' "${!v[@]}" "${v[@]}" |
jq -Rs 'split("\u0000") | . as $v | (length / 2) as $n | reduce range($n) as $idx ({}; .[$v[$idx]]=$v[$idx+$n])'
}
assoc2json dict
slurp
如果是一个比较简单的场景,我们可以通过-s/--slurp
来生成一个数组对象。比如:
$ echo 1 2 3 | jq -s
#[
# 1,
# 2,
# 3
#]
或者搭配一些内置的函数使用:
$ echo 1 2 3 | jq -s 'add'
# 6
reduce
reduce 在有大量数据输入时效率会优于 slurp 的方式。 这是一个非常高效的方式去做一些处理。
for ((i=0; i<10; i++));do
echo $i
done | jq -n 'reduce inputs as $l (0; . +($l))'
#45