在shell脚本中,当我们需要对JSON数据(例如ceph、kubernetes等一些命令的输出,或是调用API获得的响应)进行处理和提取时,如果使用传统的文本三剑客sed、awk和grep,命令将会非常臃肿不可读。虽然这三个命令在处理松散结构的数据时很有用,但当面对JSON这类数据时,却显得不够优雅。
jq就是这样一个命令行工具,专门用于处理JSON数据,特别适用于shell脚本。
jq命令的使用
首先我们需要安装jq命令:yum install jq -y
创建如下的示例文件(一张学生成绩表):
json
# cat test.json
[
{ "name": "zhangsan", "gender": "male", "score": 80 },
{ "name": "lisi", "gender": "male", "score": 90 },
{ "name": "wangerma", "gender": "female", "score": 100 }
]
jq命令的标准语法为jq [options] <jq filter> [file...]
,最简单的过滤器为.
,它将输入原封不动地转换为输出(会做格式化、高亮),例如:
json
# jq '.' test.json
[
{
"name": "zhangsan",
"gender": "male",
"score": 80
},
{
"name": "lisi",
"gender": "male",
"score": 90
},
{
"name": "wangerma",
"gender": "female",
"score": 100
}
]
这个命令可以用于检测输入是否为合格的json,例如将test.json修改为:
json
# cat test.json
[
{ "name": "zhangsan", "gender": "male", "score": 80 },
{ "name": "lisi", "gender": "male", "score": 90 },
{ "name": "wangerma", "gender": "female", "score": 100 }
则命令会报错:
shell
# jq '.' test.json
parse error: Unfinished JSON term at EOF at line 6, column 0
所以我们可以写如下shell作判断:
shell
#!/bin/bash
if jq '.' test.json >/dev/null 2>&1
then
echo "valid json"
else
echo "invalid json"
fi
检索键值
如果我们将需要获取所有学生的姓名列表,如下:
json
[
"zhangsan",
"lisi",
"wangerma"
]
该怎么做?为此我们需要先使用数组迭代器.[]
来获取列表中每一项值:
json
# jq '.[]' test.json
{
"name": "zhangsan",
"gender": "male",
"score": 80
}
{
"name": "lisi",
"gender": "male",
"score": 90
}
{
"name": "wangerma",
"gender": "female",
"score": 100
}
然后获取每一项输出的name
键值,这里使用我们熟悉的管道符|
:
json
# jq '.[] | .name' test.json
"zhangsan"
"lisi"
"wangerma"
如果想要去掉输出中的双引号,可以使用-r
参数输出原始字符串:
json
# jq -r '.[] | .name' test.json
zhangsan
lisi
wangerma
接下来我们可以在过滤器的最外层加上[]
来讲结果转换为列表:
json
# jq '[.[] | .name]' test.json
[
"zhangsan",
"lisi",
"wangerma"
]
以上过滤器的表达式看起来似乎有些臃肿了,好在jq提供了map
函数,因此我们可以将上述表达式替换为:
json
# jq 'map(.name)' test.json
[
"zhangsan",
"lisi",
"wangerma"
]
使用add函数对键值进行求和
仿照上述语法,我们可以可以获得所有学生的成绩列表:
json
# jq 'map(.score)' test.json
[
80,
90,
100
]
同样。使用管道符|
接add
函数,即可轻松获取所有学生的成绩总和:
json
# jq 'map(.score) | add' test.json
270
使用select获取部分键值
如果我们想要对所有男生的成绩进行求和,就需要使用select(condition)
函数了,所有condition
为true
的输入会被保留,false
则会被丢弃,如我们获取所有性别为男的数据gender == "male"
:
json
# jq 'map(select(.gender == "male"))' test.json
[
{
"name": "zhangsan",
"gender": "male",
"score": 80
},
{
"name": "lisi",
"gender": "male",
"score": 90
}
]
进一步获取所有男生的分数:
json
# jq 'map(select(.gender == "male").score)' test.json
[
80,
90
]
最后求和:
json
# jq 'map(select(.gender == "male").score) | add' test.json
170
将输入转换为新的格式输出
如果我们想将上述步骤中的输出合并为一个新的json数据并输出,则可以使用如下方式进行格式化输出:
json
# jq '{ students: map(.name), totalscore: map(.score) | add, totalmalescore: map(select(.gender == "male").score) | add }' test.json
{
"students": [
"zhangsan",
"lisi",
"wangerma"
],
"totalscore": 270,
"totalmalescore": 170
}
总结
在shell脚本中处理json数据,jq命令是个很好的选择。
本教程将对这个命令的用法只进行了简单入门介绍,如需了解详细语法,可以参看官方文档:https://jqlang.github.io/jq/manual/