jq 的工作方式,是让传入的 JSON 数据经过一个单一表达式(写成一条过滤器管道),得到想要的转换结果。
jq命令行选项速览jq语言由jq程序实现。
这个程序提供了几个方便的命令行选项,用来控制输入怎样被读取、输出怎样呈现。
下面的例子里你会遇到:
-n或--null-input
通常jq程序会拿到一个要读取的文件,或者你向它的输入发送数据。
--null-input选项让你不需要任何输入就能生成 JSON 数据。
-c或--compact-output
默认情况下,jq会美化它的输出。
数据排版整齐时,对人查看非常有用。
但机器并不需要:--compact-output选项会去掉格式化用的空白,把生成的 JSON 体积降到最小。
-f filename或--from-file filename
从filename读取jq程序,而不是在命令行上直接提供它。
sed和awk也都用-f选项做同样的事。
你会在练习题的测试脚本里看到它的用法。
关于所有选项的细节,参见手册。
过滤器也叫表达式。
一个过滤器接收输入并产生输出。
就像你在 Unix shell 里工作那样,你可以用管道|把多个过滤器连接起来,让一个过滤器的输出接到另一个的输入。
.
这是最简单的过滤器。
它只是把输入原样传给输出。
比如,jq默认会美化输出,所以把 JSON 交给.过滤器,就能免费得到排版整齐的输出!
$ echo '[1, 2, 3]' | jq '.'
[
1,
2,
3
]
这里先快速介绍一下如何使用数组。 我们稍后会更详细地讲这个主题。
数组元素用方括号访问,下标从 0 开始。
$ echo '[10, 20, 30]' | jq '.[1]'
20
过滤器可以把表达式包在[和]里来构造数组:
用一组已知的元素:
jq -n '[1, 2, 3]'
用来收集元素流:比如,range是一个输出数字数据流的函数
$ jq -n 'range(10; 70; 15)'
10
25
40
55
用[]可以把表达式的结果收集成一个数组
$ jq -c -n '[range(10; 70; 15)]'
[10,25,40,55]
逗号并不只是分隔数组元素的语法。 逗号是一个把数据流连接起来的运算符。
比如,[1, 2, 3]是一个过滤器:它用数组构造器[],把1、2、3这三个表达式连接后的结果收集起来。
你注意到上面range(10; 70; 15)里的分号了吗?
因为在jq语言里逗号有专门的用途,所以接受多个参数的函数用分号来分隔参数。
快速介绍一下对象。
和许多编程语言类似,用点来访问对象的属性
$ echo '{"foo": {"bar": "qux"}}' | jq '.foo.bar'
"qux"
方括号也可以用在对象上,不过这时字符串字面量需要加引号。 这是处理含空格的键的一种方法。
$ echo '{"foo bar": "qux"}' | jq '.["foo bar"]'
"qux"
你可以用{}和key: value键值对来构造对象。
“简单”字符串的键不需要加引号。
jq -n '{question: (6 * 9), answer: 42}'
输出
{
"question": 54,
"answer": 42
}
要把键当作表达式处理,必须把它包在圆括号里(下面这段的输出与上面相同)。
echo '[["question", "answer"], [54, 42]]' \
| jq '{(.[0][0]): .[1][0], (.[0][1]): .[1][1]}'
从一个大对象里取出其中一部分键,是很常见的需求。
比如,要从下面这个对象里取出id和name
{
"id": 101,
"name": "alpha widget",
"specifications": {...}
}
我们可以这样写
{id: .id, name: .name}
但这种情况太常见了,所以有简写语法:
{id, name}
比如,假设file.json的内容是
{
"key1": "value1",
"key2": [5, 15, 25]
}
我们来算一下 key2 这个数组的长度:
$ jq '.key2 | length' file.json
3
我们把.key2表达式的输出通过管道传给length作为输入,不出所料,它会输出数组中元素的个数。
这是jq里需要花点时间适应的一点:大多数(但不是全部)函数都像过滤器那样工作,你把数据交给过滤器的输入,而不是作为参数传进去。
在这个例子里,输入的 JSON 数据被忽略,对输出没有任何影响:
$ echo '{"answer": 42}' | jq '6 * 9'
54
一个过滤器可以输出不止一个值。
比如,.[]这个过滤器会把数组的每个元素分别作为单独的值输出:
$ jq -n -c '[1, 2, 3]'
[1,2,3]
$ jq -n -c '[1, 2, 3] | .[]'
1
2
3
把这样的过滤器用管道接到另一个过滤器上,第 2 个过滤器会**对每个值**执行:
$ jq -n -c '[1, 2, 3] | .[] | . * 2'
2
4
6
这有点像是在隐式地迭代。
一旦理解了这种技巧,你就会发现威力强大的jq过滤器可以写得非常简洁。
圆括号用来把子表达式组合在一起,以确定运算顺序,和其他语言一样。
在jq里,需要用到它们的地方有时会显得有些出人意料。
比如,假设我们要构造一个有 2 个元素的数组:9 的平方根,以及e的 1 次方。
这两个单独的表达式是9 | sqrt和1 | exp。
我们期望输出是数组[3, 2.7...]
$ jq -n '[ 9|sqrt, 1|exp ]'
[
20.085536923187668,
2.718281828459045
]
为什么没有得到我们期望的结果?jq是这样理解它的:
[ ((9|sqrt), 1) | exp ]
jq构造出一条包含两个元素(3和1)的数据流,它们分别被交给exp。
我们需要确保exp只接收一个数字作为输入。
换句话说,我们要保证管道先于逗号求值。
$ jq -n '[ 9|sqrt, (1|exp) ]'
[
3,
2.718281828459045
]
摘自手册
jq 支持与 JSON 相同的一组数据类型:数字、字符串、布尔值、数组、对象(用 JSON 的话说,就是以字符串为键的哈希),以及 "null"。
你会在后续的练习里进一步了解这些内容。
空白在jq里并不重要。
你可以按自己的喜好用空格、制表符和换行来排版代码。
据我们所知,目前还没有现成的jq风格指南。
jq里的值都是不可变的。
修改值的过滤器会输出一个新的值。
这意味着jq没有全局变量,你需要习惯把一个过滤器里的状态传给另一个过滤器。
false和null这两个值被视为 false,其他任何值(包括数字 0,以及空的字符串、数组、对象)都为 true。
这里不深入展开(函数会是另一道练习的主题),先介绍几个有用的内置函数:
输入一个数组,输出数组中元素的个数。
$ jq -n '[10, 20, 30, 40] | length'
4
这个运算符会根据操作数的类型做不同的事: 把数字相加, 把字符串拼接, 把数组追加, 把对象合并。
$ jq -c -n '
3 + 4,
"foo" + "bar",
["a", "b"] + ["c"],
{"m": 10} + {"n": 20}
'
7
"foobar"
["a","b","c"]
{"m":10,"n":20}
add是一个函数,它接收一个数组,返回把其中所有元素按+的规则加在一起的结果。
[1, 2, 3] | add会输出6。
输入一个数组,并把一个过滤器作为参数,输出一个数组,其中每个元素都应用了该过滤器
$ jq -c -n '[10, 20, 30, 40] | map(. / 5)'
[2,4,6,8]
给定某个输入,并把一个过滤器作为参数:
null值,而是真正没有任何输出)比如,给定一些数字,选出能被 3 整除的那些
$ jq -n 'range(10) | select(. % 3 == 0)'
0
3
6
9
回想一下,range输出的是一条数字数据流。
select会针对每个数字各被调用一次。
只有“通过”该表达式的数字才会被输出。
你经常需要从数组中选出元素。 有几种做法。
输入为["Anne", "Bob", "Cathy", "Dave"]时,选出长度为 4 的名字。
把map和select一起用
map(select(length == 4))
把数组拆成一个个元素,对这条数据流做select,再收集结果
[ .[] | select(length == 4) ]
注释以#字符开头,一直到行尾结束。