我们假设你已经熟悉 JSON 语法和数据类型。
jq 的工作方式是让传入的 JSON 数据经过一个_单一表达式_(写作_过滤器管道_),从而得到你想要的转换后的数据。
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选项来达到同样的目的。你会在练习的测试脚本里看到它的用法。
本课的其余部分将聚焦于jq_语言_本身。
过滤器也叫表达式。
一个_过滤器_接收输入并产生输出。就像你在 Unix shell 里工作的方式,你可以用管道|把多个_过滤器_连接起来,让一个_过滤器_的输出接到另一个_过滤器_的输入上。
.
这是最简单的_过滤器_。它只是把输入原样传给输出。
$ 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 '[{"key":"question", "value":54}, {"key":"answer", "value":42}]' \
| jq '{(.[0].key): .[0].value, (.[1].key): .[1].value}'
例如,假设file.json的内容是
{
"key1": "value1",
"key2": [5, 15, 25]
}
我们来计算一下 key2 这个_数组_的长度:
$ jq '.key2 | length' file.json
3
我们把.key2表达式的输出_通过管道_传给length作为输入,它不出所料地输出了_数组_中元素的个数。
在这个例子里,输入的 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
把这样一个_过滤器_用管道接到另一个_过滤器_上,就会**对每个值**执行第二个_过滤器_:
$ 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
]
这里不深入展开(函数会是另一个练习的主题),下面是一些有用的内置函数:
length
给定一个数组作为输入,输出数组中元素的个数。
$ 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。
map
给定一个数组作为输入、一个过滤器作为实参,输出一个把该过滤器应用到每个元素上的数组
$ jq -c -n '[10, 20, 30, 40] | map(. / 5)'
[2,4,6,8]
select
给定_某些_输入和一个过滤器作为实参:
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) ]
注释以#字符开头,一直延续到行尾。
你突然很想吃薄煎饼。 作为一个喜欢钻研技术的现代人,你用 JSON 格式写了一份薄煎饼食材的购物清单。 清单的结构如下:
{
"name": "name of shopping list",
"ingredients": [ ...list of ingredients... ],
"optional ingredients": [ ...list of ingredients... ]
}
每一种食材都用一个 JSON 对象表示,例如:
{
"item": "flour",
"amount": {
"quantity": 2,
"unit": "cup"
}
}
食材还有一个名为 "substitute" 的可选属性,它的值是一个字符串,表示可以用来替代它的另一种食材。
我们来看看如何用jq查看这份购物清单。
写一个表达式,输出购物清单的 "name" 元素。
只统计“必需”的食材。不要算上可选食材。
写一个表达式,输出糖的用量。 只需要数字部分。
有些食材是可以替换的(如果没有食材 X,可以用 Y 代替)。 输出一个 JSON 对象,把推荐的食材映射到它的替代品。 映射中要包含可选食材。