在命令行中优雅地处理JSON
涛叔当年刚出道的时候,整天使用 grep/sed/awk 处理各种文本日志。那时候的日志都是一行一条,内容使用特定分割符。使用各种 unix 命令配合管道真可谓得心应手。然而,到了 9102 年,好多数据都以 json 形式传输和存储。awk 这类工具在 json 面前就有点力不从心了,毕竟是几十年前开发的工具。那有没有针对 json 专门开发的工具呢?答案是肯定的,这就是本文要介绍的 jq1 命令。
jq 支持各种 unix 系统,请自行安装。现在单讲几种使用场景。
先来一个段 json 数据,内容如下(并写入 a.json 文件):
{"code":0,"message":"0","ttl":1,
"data":{"results":[
{"item_id":1342,"type":1},
{"item_id":1785,"type":2},
{"item_id":1413,"type":3}]}}
首先要做的就是格式化,这是 jq 最简单的功能:
cat a.json | jq '.'
输出效果如下(还有语法高亮):
如果 json 内容很长,则可以使用管道传给 less 命令:
cat a.json | jq '.' | less
如果你自己试一下就会发现,less 展示的结果没有颜色,这怎么能忍?
终端下的颜色是由转义序列控制的。有兴趣的同学可以参考我的另一篇文章多彩的终端。像 jq 这类的工具会检测输出目的地是否为终端环境(tty),如果不是(比如管道或普通文件) 就不会输出颜色转义序列,否则这些转义序列会破坏文件内容。
但如果确定读写双方都支持转义序列,我们就可以强制开启颜色输出:
jq -C '.' a.json|less -R
这里用到了 jq 的 -c
参数和 less 的 -R
参数。
现在介绍 jq 的高级操作。还是以上面的 json 为例。
{
"code": 0,
"message": "0",
"ttl": 1,
"data": {
"results": [
{
"item_id": 1342,
"type": 1
},
{
"item_id": 1785,
"type": 2
},
{
"item_id": 1413,
"type": 3
}
]
}
}
显然,这是一个比较复杂的数据结构。我们可以使用 jq 提取部分字段
$ cat a.json | jq '.code, .ttl'
0
1
提取字段使用.
操作,提取多个字段可以使用,
连接。
如果我们想提取 results 内容,则可以这样:
$ cat a.json | jq '.data.results'
[
{"item_id": 1342,
"type": 1
},
{
"item_id": 1785,
"type": 2
},
{
"item_id": 1413,
"type": 3
}
]
是不是清真多了?
传统的 unix 工具是以行为单位,而 json 数组的每个元素可能有复杂的数据结构(多行),我们能否以元素为单位处理 json 数据呢?当然可以。
提取数组的元素字段
$ cat a.json | jq '.data.results' | jq '.[].item_id'
1342
1785
1413
提取多个数组元素字段
$ cat a.json | jq '.data.results' | jq '.[]|.item_id,.type'
1342
1
1785
2
1413
3
结果是 item_id 和 type 交替出现,传统的 unix 工具依然不好处理。能将同一元素的不同字段放到一行吗?当然可以:
$ cat a.json | jq '.data.results' |jq '.[] |"item_id:\(.item_id) type:\(.type)"'
"item_id:1342 type:1"
"item_id:1785 type:2"
"item_id:1413 type:3"
这里用到了所谓的 string interpolation 语法,有点像 swift 语言。好了,现在可以配合其他 uinx 工具对数据进行处理了。
jq 还支持很多高级特性,有兴趣的同学可以参考官方手册2。本文的重点是介绍 jq 最核心的 20% 功能,基本可以解决 80% 的问题。
@2023 再补充一个过滤功能。比如我们可以使用 select 语法只输出某个 type 的数据
$ cat a.json | jq '.data.results|.[]|select(.type==2)|.item_id,.type'
1785
2
先写这么多,有问题请留言讨论。