如何在 Linux 命令行上使用 jq 解析 JSON 文件


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数据的结构。

整个对象被花括号包围。它包含两个键值对:messagetimestamp。还有一个名为iss_position的对象,它包含latitudelongitude两个键值对。

我们再试一次。这次,我们将输出重定向到名为“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的数组。我们知道它是一个数组,因为其以左中括号 ([) 开头 (上图红色高亮部分)。它是一个对象数组,每个对象包含两个键值对:namecraft

和之前一样,我们可以使用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函数根据应用的内容给出不同的指标,例如: