JSON与jq工具
JSON,即JavaScript对象表示法,是目前网络数据传输中最受欢迎的文本格式之一。它被广泛应用,你很可能会遇到它。本文将向您展示如何在Linux命令行中使用jq
命令来处理JSON数据。
JSON简介
JSON是一种利用纯文本文件来编码数据的方案,其特点是自描述性。在JSON文件中,不存在注释,内容本身应该清晰易懂。每个数据值都关联一个文本字符串,称为“名称”或“键”,用于标识数据的含义。它们共同构成名称-值对或键-值对。键与值之间用冒号(:)分隔。
“对象”是键值对的集合。在JSON文件中,对象以左大括号({)开始,以右大括号(})结束。JSON还支持“数组”,即值的有序列表。数组以左中括号([)开始,以右中括号(])结束。
从这些简单的定义出发,可以构建任意复杂的数据结构。例如,对象可以嵌套在对象中,对象可以包含数组,数组也可以包含对象。这些嵌套可以是多层次的。然而,在实际应用中,如果JSON数据的结构过于复杂,可能需要重新考虑数据布局的设计。但如果只是使用现有的JSON数据,我们只能接受其既有的布局并进行处理。
大多数编程语言都拥有解析JSON数据的库或模块。然而,Bash shell却缺乏这样的功能。
为了满足这一需求,jq实用工具应运而生!借助jq,我们可以方便地在Bash shell中解析JSON数据,无论JSON数据的结构是清晰简洁还是复杂混乱。
如何安装 jq
为了演示本文中的示例,我们需要在所有Linux发行版上安装jq。
在Ubuntu上安装jq,请运行以下命令:
sudo apt-get install jq
在Fedora上安装jq,请运行以下命令:
sudo dnf install jq
在Manjaro上安装jq,请运行以下命令:
sudo pacman -Sy jq
如何使 JSON 可读
JSON格式对空白字符不敏感,因此其布局不会影响其含义。只要符合JSON语法规则,处理JSON的系统就可以正常读取和理解它。因此,JSON通常作为不带布局的长字符串进行传输,以节省空间。然而,这种格式不利于人工阅读。
让我们从美国国家航空航天局(NASA)的网站获取一些JSON数据。我们将使用一个API接口,它会返回国际空间站的当前位置信息。我们将使用curl
命令来获取JSON数据。
为了忽略curl
命令输出的状态信息,我们使用-s
(静默)选项运行以下命令:
curl -s http://api.open-notify.org/iss-now.json
虽然可以阅读这段输出,但并不方便。现在,我们将其通过管道传递给jq工具。
jq使用过滤器来解析JSON数据。最简单的过滤器是点号(.),它表示“打印整个对象”。默认情况下,jq会对输出进行美化处理。
我们将命令组合起来,并输入以下内容:
curl -s http://api.open-notify.org/iss-now.json | jq .
现在,输出的可读性大大提高!我们可以清晰地看到JSON数据的结构。
整个对象被花括号包围。它包含两个键值对:message
和timestamp
。还有一个名为iss_position
的对象,它包含latitude
和longitude
两个键值对。
我们再试一次。这次,我们将输出重定向到名为“iss.json”的文件:
curl -s http://api.open-notify.org/iss-now.json | jq . > iss.json
cat iss.json
这样,我们就得到了一个格式化好的JSON对象副本,存储在硬盘驱动器上。
访问数据值
正如我们所见,jq可以从JSON数据中提取值,这些值可以通过管道传递或存储在文件中。为了简化命令行操作,我们将使用本地文件来避免curl命令的干扰。
从JSON文件中提取数据的最简单方法是指定键名来获取对应的值。输入点号(.)和键名,它们之间没有空格。这将创建一个基于键名的过滤器。还需要告诉jq使用哪个JSON文件。
我们输入以下内容来获取消息值:
jq .message iss.json
jq在终端窗口中打印消息值的文本。
如果键名包含空格或标点符号,则必须将其过滤器用引号括起来。为了避免问题,通常应避免在JSON键名中使用特殊字符,而仅使用字符、数字和下划线。
首先,我们输入以下内容来获取时间戳的值:
jq .timestamp iss.json
在终端窗口中获取并打印时间戳的值。
如何访问iss_position
对象中的值?我们可以使用JSON的点表示法。在键值的“路径”中包含iss_position
对象名称。为此,包含键的对象名称应位于键本身的名称之前。
我们输入以下内容,包括纬度键名(请注意 .iss_position
和 .latitude
之间没有空格):
jq .iss_position.latitude iss.json
要提取多个值,您必须执行以下操作:
- 在命令行中列出键名。
- 用逗号(,)分隔它们。
- 用双引号(“)或单引号(‘)将它们括起来。
考虑到这一点,我们键入以下内容:
jq ".iss_position.latitude, .timestamp" iss.json
这两个值被打印到终端窗口。
使用数组
让我们从NASA获取另一个JSON对象。
这次,我们将使用当前在太空中的宇航员名单:
curl -s http://api.open-notify.org/astros.json
运行成功,现在让我们再执行一次。
我们将输入以下内容,通过管道将JSON数据传递给jq,并将其重定向到名为“astro.json”的文件:
curl -s http://api.open-notify.org/astros.json | jq . > astro.json
现在,我们输入以下命令来检查我们的文件:
less astro.json
如下图所示,我们看到了在太空的宇航员名单,以及他们所在的航天器。
这个JSON对象包含一个名为people
的数组。我们知道它是一个数组,因为其以左中括号 ([) 开头 (上图红色高亮部分)。它是一个对象数组,每个对象包含两个键值对:name
和craft
。
和之前一样,我们可以使用JSON的点表示法来访问值。我们还需要在数组的名称中包含中括号([])。
考虑到这一点,我们键入以下内容:
jq ".people[].name" astro.json
这次,所有的名字值都打印到了终端窗口。我们让jq打印数组中每个对象的名称值。这很方便,不是吗?
如果我们在括号中放入它在数组中的位置,我们就可以检索单个对象的名称。 数组使用零偏移索引,这意味着数组中第一个位置的对象索引为零。
要访问数组中的最后一个对象,可以使用-1;要获取数组中倒数第二个对象,可以使用-2,依此类推。
有时,JSON对象会提供数组中元素的数量,如本例所示。除了数组,它还包含一个名为number的键值对,其值为6。
此数组包含以下数量的对象:
jq ".people[1].name" astro.json
jq ".people[3].name" astro.json
jq ".people[-1].name" astro.json
jq ".people[-2].name" astro.json
您还可以在数组中指定开始和结束对象来获取一个范围。这被称为“切片”,可能有点令人困惑。请记住,数组使用零偏移索引。
要从索引位置2检索对象,直到(但不包括)索引位置4的对象,我们键入以下命令:
jq ".people[2:4]" astro.json
这将打印数组索引二(数组中的第三个对象)和三(数组中的第四个对象)处的对象。它会在数组索引4处停止处理,这是数组中的第五个对象。
更好地理解这一点的方法是在命令行上进行实验。你会很快明白它是如何工作的。
如何使用带过滤器的管道
您可以将一个过滤器的输出传递到另一个过滤器,无需学习新语法。就像Linux命令行一样,jq使用竖线(|)来表示管道。
我们将让jq将people数组传递给.name过滤器,这将列出终端窗口中的宇航员姓名。
我们输入以下内容:
jq ".people[] | .name" astro.json
创建数组和修改结果
我们可以使用jq创建新对象,例如数组。在此示例中,我们将提取三个值并创建一个包含这些值的新数组。请注意,开括号 ([) 和闭括号 (]) 也是过滤器字符串中的第一个和最后一个字符。
我们输入以下内容:
jq "[.iss_position.latitude, .iss_position.longitude, .timestamp]" iss.json
输出用括号括起来,并用逗号分隔,使其成为正确格式的数组。
数值也可以在检索时进行操作。让我们从ISS位置文件中提取时间戳,然后再次提取它并更改返回值。
为此,我们键入以下内容:
jq ".timestamp" iss.json
jq ".timestamp - 1570000000" iss.json
如果您需要从值数组中添加或删除标准偏移量,这将非常有用。
让我们输入以下内容来提醒自己iss.json文件包含的内容:
jq . iss.json
假设我们想要删除message
键值对。它与国际空间站的位置无关。它只是表示位置已成功检索的标志。我们可以将其省略(你也可以忽略它)。
我们可以使用jq的del()
函数来删除键值对。要删除message
键值对,我们输入以下命令:
jq "del(.message)" iss.json
请注意,这实际上并没有从“iss.json”文件中删除它;它只是从命令的输出中删除它。如果需要创建一个不包含message
键值对的新文件,请运行该命令,然后将输出重定向到新文件中。
更复杂的JSON对象
让我们获取更多NASA数据。这次,我们将使用一个包含来自世界各地的流星撞击地点信息的JSON对象。这是一个比我们之前处理的更大的文件,其JSON结构也更复杂。
首先,我们将键入以下内容将其重定向到一个名为“strikes.json”的文件:
curl -s https://data.nasa.gov/resource/y77d-th95.json | jq . > strikes.json
要查看JSON的样子,我们输入以下内容:
less strikes.json
如下图所示,该文件以左中括号([)开头,因此整个对象是一个数组。数组中的对象是键值对的集合,并且还有一个名为geolocation
的嵌套对象。geolocation
对象包含更多的键值对,以及一个名为coordinates
的数组。
让我们从数组中索引位置995到末尾的对象中检索流星撞击点的名称。
我们将输入以下命令,通过三个过滤器来传递JSON数据:
jq ".[995:] | .[] | .name" strikes.json
过滤器的工作方式如下:
.[995:]
:这告诉jq处理从数组索引995到数组末尾的对象。冒号(:)后面没有数字告诉jq继续到数组的末尾。.[]
: 这个数组迭代器告诉jq处理数组中的每个对象。.name
:此过滤器提取名称值。
稍作修改,我们可以从数组中提取最后10个对象。“-10”指示jq从数组末尾开始处理10个对象。
我们输入以下内容:
jq ".[-10:] | .[] | .name" strikes.json
就像我们在前面的示例中所做的那样,我们可以通过输入以下内容来选择单个对象:
jq ".[650].name" strikes.json
我们还可以对字符串应用切片。为此,我们将输入以下命令来请求数组索引234处对象名称的前四个字符:
jq ".[234].name[0:4]" strikes.json
我们还可以完整地查看特定的对象。为此,我们输入以下命令并包含一个没有任何键值过滤器的数组索引:
jq ".[234]" strikes.json
如果您只想查看值,可以在没有键名的情况下执行相同的操作。
对于我们的示例,我们输入以下命令:
jq ".[234][]" strikes.json
为了从每个对象中检索多个值,我们在以下命令中用逗号分隔它们:
jq ".[450:455] | .[] | .name, .mass" strikes.json
如果要检索嵌套值,则必须识别形成它们的“路径”的对象。
例如,要引用坐标值,我们必须包含外层数组、geolocation嵌套对象和嵌套的coordinates数组,如下所示。
要查看数组索引位置121处对象的坐标值,我们输入以下命令:
jq ".[121].geolocation.coordinates[]" strikes.json
长度函数
jq的length
函数根据应用的内容给出不同的指标,例如: