基础

基础 属于 jq

1 个练习

关于 基础

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]}'
Note

从一个大对象里取出其中一部分键,是很常见的需求。 比如,要从下面这个对象里取出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作为输入,不出所料,它会输出数组中元素的个数。

Caution

这是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。

函数与运算符

这里不深入展开(函数会是另一道练习的主题),先介绍几个有用的内置函数:

  • 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

    给定某个输入,并把一个过滤器作为参数:

    • 如果对该参数应用过滤器得到true值,就原样输出输入
    • 否则不输出任何东西(不是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) ]
      

注释

注释以#字符开头,一直到行尾结束。

通过 GitHub 编辑 该链接会在新窗口或标签页中打开

学习 基础