JQ简介

617 阅读6分钟

JQ简介

背景:手指、头部和谷歌

每当我到达工作的停止点时,我都会使用名为gwip1的 bash 别名来创建“正在进行的工作”提交。它发生在我没有有意识地思考的情况下。就像我的手指知道 vim 键绑定一样,他们知道gwip.

其他动作,我知道它们是如何工作的,但我每次都必须考虑它们。它们在我的脑海中,而不是我的手指。

然而,有些东西永远不会留在我的脑海里,也不会留在我的手指上,我每次都必须用谷歌搜索它们。jq是其中之一。

我知道这是一个强大的工具,但我总是回到谷歌,然后从某个地方复制和粘贴解决方案。所以我解决了我的问题,但从来没有学习过这个工具。

是时候解决这个问题了。**在本文中,我将jq深入介绍基本构建块,以便您能够理解 jq 的工作原理。**当然,您有时仍可能需要前往 google 查找函数名称或检查您的语法,但至少您将有扎实的基础知识。

什么是 JQ

jq是一个轻量级的命令行 JSON 处理器。我使用 brew ( brew install jq)安装它,但它是一个可移植的可执行文件,因此很容易在 Linux、Windows 或 macOS 上安装。要使用它,您需要构建一个或多个过滤器,并将这些过滤器应用于 JSON 文档。

最简单的过滤器是身份过滤器,它返回其所有输入 ( .):

$ echo '{"key1":{"key2":"value1"}}' | jq '.'
{
  "key1": {
    "key2": "value1"
  }
}

这个过滤器对于漂亮地打印 JSON 文档非常方便。3我将忽略漂亮的打印并直接使用jq来转换 JSON 文档。

使用 JQ 选择元素

我将用于jq过滤 GitHub 存储库 API 返回的数据。我默认返回的数据如下所示:

$ curl https://api.github.com/repos/stedolan/jq
{
  "id": 5101141,
  "node_id": "MDEwOlJlcG9zaXRvcnk1MTAxMTQx",
  "name": "jq",
  "full_name": "stedolan/jq",
  "private": false,
  "owner": {
    "login": "stedolan",
    "id": 79765
  },
  "html_url": "https://github.com/stedolan/jq",
  "description": "Command-line JSON processor",
  "stargazers_count": 19967,
  "watchers_count": 19967,
  "language": "C",
  "license": {
    "key": "other",
    "name": "Other",
    "spdx_id": "NOASSERTION",
    "url": null,
    "node_id": "MDc6TGljZW5zZTA="
  }
}

jq 让我们将 JSON 文档视为一个对象并选择其中的元素。

下面是我如何过滤 JSON 文档以选择name键的值:

$ curl https://api.github.com/repos/stedolan/jq | jq ' .name' 
"jq"

同样,对于选择owner键的值:

$ curl https://api.github.com/repos/stedolan/jq | jq ' .owner' 
{
    "login": "stedolan",
    "id": 79765
}

您可以像这样钻入尽可能远的地方:

$ curl https://api.github.com/repos/stedolan/jq | jq ' .owner.login' 
"stedolan"

我学到了什么:对象标识符-索引

jq允许您选择 JSON 文档中的元素,就像它是 JavaScript 对象一样。只需从.( 对于整个文档) 开始并深入到您想要的值。它最终看起来像这样:

jq '.key.subkey.subsubkey'

使用 JQ 选择数组

如果你curl使用 GitHub Issues API,你会得到一系列问题:

$ curl https://api.github.com/repos/stedolan/jq/issues?per_page=5    
[
  {
    "id": 966024429,
    "number": 2341,
    "title": "Question about license.",
    "body": "I would like to create a [winget](https://github.com/microsoft/winget-cli) package for jq. 🙏🏻"
  },
  {
  
    "id": 962477084,
    "number": 2340,
    "title": "visibility of wiki pages",
    "body": "The visibility of wiki pages to search engines is generally limited; for example, the search result for \"jq Cookbook\" looks like this:"
  },
  {
   
    "id": 955350543,
    "number": 2337,
    "title": "Release 1.6 does not have pre-autoreconf'ed configure script",
    "body": "If you have a usage question, please ask us on either Stack Overflow (https://stackoverflow.com/questions/tagged/jq) or in the #jq channel (http://irc.lc/freenode/%23jq/) on Freenode (https://webchat.freenode.net/)."
  },
  {
    "id": 954792209,
    "number": 2336,
    "title": "Fix typo",
    "body": ""
  },
  {
    "id": 940627936,
    "number": 2334,
    "title": "Compile error messages don't provide column only line number",
    "body": "Compile errors in filter expressions don't include the column number where the parser approximately or exactly locates the error. Most filter expressions are one-liners (are multiple lines even supported?), so the information that the error is on line 1 is not helpful."
  }
]

要获取数组中的特定元素,请给出jq索引:

$ curl https://api.github.com/repos/stedolan/jq/issues?per_page=5 | jq '.[4]' 
 {
    "id": 940627936,
    "number": 2334,
    "title": "Compile error messages don't provide column only line number",
    "body": "Compile errors in filter expressions don't include the column number where the parser approximately or exactly locates the error. Most filter expressions are one-liners (are multiple lines even supported?), so the information that the error is on line 1 is not helpful."
  }

旁注:数组索引 jq

数组索引有一些有用的便利语法。

您可以选择范围:

$ echo "[1,2,3,4,5]" | jq '.[2:4]'
[3,4]

您可以选择一侧范围:

$ echo "[1,2,3,4,5]" | jq '.[2:]'
[3,4,5]

此外,您可以使用否定从末尾进行选择:

$ echo "[1,2,3,4,5]" | jq '.[-2:]'
[4,5]

您可以将数组索引与对象索引一起使用:

$ curl https://api.github.com/repos/stedolan/jq/issues?per_page=5 | jq '.[4].title' 
"Compile error messages don't provide column only line number"

您可以使用[]来获取数组中的所有元素。例如,以下是我如何获取 API 请求返回的问题的标题:

$ curl https://api.github.com/repos/stedolan/jq/issues?per_page=5 | jq '.[].title' 
"Question about license."
"visibility of wiki pages"
"Release 1.6 does not have pre-autoreconf'ed configure script"
"Fix typo"
"Compile error messages don't provide column only line number"

我学到了什么:数组索引

jq允许您选择整个数组[]、特定元素[3]或范围,[2:5]并在需要时将它们与对象索引组合。

它最终看起来像这样:

jq '.key[].subkey[2]

旁注:从 JQ 输出中删除引号

如果需要,中的-r选项jq会为您提供原始字符串。

$ echo '["1","2","3"]' | jq -r '.[]'
1
2
3

-j(用于连接)选项可将输出结合在一起。

$ echo '["1","2","3"]' | jq -j '.[]'
123

将元素放入数组中 jq

一旦开始使用数组索引来选择元素,就会遇到一个新问题。返回的数据将不是有效的 JSON 文档。在上面的示例中,问题标题以新行分隔:

"Question about license."
"visibility of wiki pages"
"Release 1.6 does not have pre-autoreconf'ed configure script"
"Fix typo"
"Compile error messages don't provide column only line number"
...

事实上,每当你要求jq返回一个未包装的元素集合时,它都会在一个新行上打印它们。您可以通过明确要求jq忽略其输入并返回两个数字来看到这一点:

$ echo '""' | jq '1,2' 
1
2

您可以像1,2在 JavaScript中将文本转换为数组一样解决此问题:通过将其包装在数组构造函数中[ ... ]

$ echo '""' | jq '[1,2]' 
[
  1,
  2
]

类似地,要将生成的结果集合放入 JSON 数组,请将其包装在数组构造函数中[ ... ]

我的 GitHub 问题标题过滤器 ( .[].title) 然后变成[ .[].title ]这样:

$ curl https://api.github.com/repos/stedolan/jq/issues?per_page=5 | \
  jq '[ .[].title ] ' 
[  "Question about license.",  "visibility of wiki pages",  "Release 1.6 does not have pre-autoreconf'ed configure script",  "Fix typo",  "Compile error messages don't provide column only line number"]

现在我有一个有效的 JSON 文档。

我学到了什么:数组构造函数

如果您的jq查询返回多个元素,它们将以换行符分隔。

$ echo '[{"a":"b"},{"a":"c"}]' | jq -r '.[].a'
  "b"
  "c"

要将这些值转换为 JSON 数组,您所做的类似于在 JavaScript 中创建数组:将值包装在数组构造函数 ( [...]) 中。

它最终看起来像这样:

$ echo '[{"a":"b"},{"a":"c"}]' | jq -r '[ .[].a ]'
[  "b",  "c"]

使用jq选择多个字段

GitHub 问题 API 有很多我不关心的细节。我想从返回的 JSON 文档中选择多个字段并将其余字段留在后面。

最简单的方法是使用,指定多个过滤器:

$ curl https://api.github.com/repos/stedolan/jq/issues?per_page=2 | \ 
  jq ' .[].title, .[].number'
"Question about license."
"visibility of wiki pages"
2341
2340

但这会返回一个又一个选择的结果。要更改顺序,我可以分解出数组选择器:

$ curl https://api.github.com/repos/stedolan/jq/issues?per_page=2 | \
  jq '.[] |  .title, .number'
"Question about license."
2341
"visibility of wiki pages"
2340

这个重构使用了一个管道 (|),我将很快讨论它,并在每个数组元素上运行我的对象选择器 (.title.number)。

如果将查询包装在数组构造函数中,则会得到以下信息:

$ curl https://api.github.com/repos/stedolan/jq/issues?per_page=2 | \
  jq '[ .[] |  .title, .number ]'
[  "Question about license.",  2341,  "visibility of wiki pages",  2340]

但这仍然不是我需要的 JSON 文档。要将这些值放入适当的 JSON 对象中,我需要一个对象构造函数{ ... }

将元素放入对象中 jq

在展示我的 GitHub 查询如何使用对象构造函数之前,让我们先看一些简单的例子。

一个小例子

我有一个包含我的名字 ( ["Adam","Gordon","Bell"])的数组,我想把它变成一个像这样的 JSON 对象:

{
  "first_name":"Adam",
  "last_name":"Bell"
}

我可以像这样使用数组索引来选择我需要的元素:

$ echo '["Adam","Gordon","Bell"]' | jq -r '.[0], .[2]'
Adam
Bell

要将这些值包装成我需要的形状,我可以用返回它们的数组索引替换这些值:

{
  "first_name":.[0],
  "last_name":.[2]
}

或者像这样在一行上:

$ echo '["Adam","Gordon","Bell"]' | jq -r '{ "first_name":.[0], "last_name": .[2]}'
{
  "first_name": "Adam",
  "last_name": "Bell"
}

此语法与在 JSON 文档中创建对象的语法相同。唯一的区别是您可以使用您建立的对象和数组查询作为值。

返回 GitHub

回到我的 GitHub API 问题,为了将数字和标题包装到一个数组中,我使用了这样的对象构造函数:

$ curl https://api.github.com/repos/stedolan/jq/issues?per_page=2 | \
  jq '[ .[] | { title: .title, number: .number} ]'
[  {    "title": "Question about license.",    "number": 2341  },  {    "title": "visibility of wiki pages",    "number": 2340  }]

我学到了什么:对象构造函数

要将您选择的元素放回 JSON 文档,您可以将它们包装在对象构造函数中{ ... }

如果你用几个选择器构建一个 JSON 对象,它最终看起来像这样:

jq '{ "key1": <<jq filter>>, "key2": <<jq filter>> }'

这与 JSON 文档中的对象的语法相同,但jq您可以使用过滤器作为值。4

使用 JQ 进行排序和计数

我的下一个问题是我想总结一些这个 JSON 数据。GitHub 返回的每个问题都有一组标签:

$ curl https://api.github.com/repos/stedolan/jq/issues/2289 | \   
  jq ' { title: .title, number: .number, labels: .labels} '
  {
    "title": "Bump jinja2 from 2.10 to 2.11.3 in /docs",
    "number": 2289,
    "labels": [
      "feature request",
      "dependencies"
    ]
  }

jq 内置函数

如果我想要按字母顺序排列这些标签,我可以使用内置sort函数。它是这样工作的:

$  echo '["3","2","1"]' | jq 'sort'
["1", "2", "3"]

这类似于我在 JavaScript 中对数组进行排序的方式:

const l = ["3","2","1"];
l.sort();

这反映JavaScript功能等内置插件可供选择,喜欢lengthreversetostring他们都可以以类似的方式使用:

$  echo '["3","2","1"]' | jq 'reverse'
["1", "2", "3"]
$  echo '["3","2","1"]' | jq 'length'
3

如果我可以将这些内置函数与我迄今为止建立的选择器结合起来,我就可以解决我的标签排序问题。所以我接下来会展示这一点。

我学到了什么:jq内置插件

jq有很多内置功能。可能要记住的东西太多了,但内置函数往往会反映 JavaScript 函数,所以在前往jq 手册之前尝试一下,你可能会很幸运。5

管道和过滤器

在我可以sort对来自 GitHub API 请求的标签进行排序之前,我需要解释管道和过滤器在jq.

jq是 UNIX 命令行意义上的过滤器。您通过管道 ( |) 将 JSON 文档传递给它,它会对其进行过滤并将其输出到标准输出。我可以轻松地使用此功能将 jq 调用链接在一起,如下所示:

echo '{"title":"JQ Select"}' | jq '.title' | jq 'length'
9

这是确定 JSON 文档中字符串长度的一种冗长但简单的方法。您可以使用相同的想法将各种jq内置函数与我目前展示的功能结合起来。但是,还有一种更简单的方法。您可以在内部使用管道jq,从概念上讲,它们的工作方式与外壳管道一样:

echo '{"title":"JQ Select"}' | jq '.title | length'
9

以下是更多示例:

  • .title | length 将返回标题的长度
  • .number | tostring 将问题编号作为字符串返回
  • .[] | .key将返回key数组中键的值(这相当于 this .[].key

这意味着对我的标签数组进行排序很简单。我可以.labels改为.labels | sort

$ curl https://api.github.com/repos/stedolan/jq/issues/2289 | \
  jq ' { title: .title, number: .number, labels: .labels | sort } '    
  {
    "title": "Bump jinja2 from 2.10 to 2.11.3 in /docs",
    "number": 2289,
    "labels": [
      "dependencies",
      "feature request"
    ]
  }

如果你只想要一个简单的标签计数:

$ curl https://api.github.com/repos/stedolan/jq/issues/2289 | \
  jq ' { title: .title, number: .number, labels: .labels | length } '    
  {
    "title": "Bump jinja2 from 2.10 to 2.11.3 in /docs",
    "number": 2289,
    "labels": 2
  }

我学到了什么:管道和过滤器

中的所有内容jq都是一个过滤器,您可以将其与管道 ( |)结合使用。这模仿了 UNIX shell 的行为。

您可以使用管道和jq内置函数从简单的操作中构建复杂的转换。

它最终看起来像这样:

jq ' .key1.subkey2[] | sort ' # sorting
jq ' .key2.subkey | length' # length of string or array
jq ' .key3 | floor | tostring | length' # and so on

使用 JQ 映射和选择

我正在查看的问题列表中有许多低质量的问题。6假设我想抓取所有标有标签的项目。这将让我跳过所有的驱动式修复我的问题问题。

不幸的是,除非您在查询中指定所有可能的标签,否则无法使用 GitHub API 做到这一点。但是,我可以通过使用jq. 但是,要做到这一点,我将需要更多jq功能。

到目前为止,我的查询如下所示:

  jq '[ .[] | { title: .title, number: .number, labels: .labels | length } ]'

我能做的第一件事就是使用map.

  jq 'map({ title: .title, number: .number, labels: .labels | length }) 

map(...)让我们解开一个数组,应用一个过滤器,然后将结果重新包装回一个数组。你可以把它看作是一种简写,[ .[] | ... ]在我的经验中它出现了很多,所以值得牢记。

我可以将它与如下所示的 select 语句结合起来:

map(select(.labels > 0))

select是一个内置函数,它接受一个布尔表达式并且只返回匹配的元素。它类似于WHERESQL 语句中的子句或 JavaScript 中的数组过滤器。

就像map,我发现select出现了很多,所以虽然你可能不得不回到这篇文章或在你需要它的前几次谷歌它,幸运的是,在那之后它会开始坚持你的记忆。

把这些放在一起看起来像这样:

curl https://api.github.com/repos/stedolan/jq/issues?per_page=100 | \
   jq 'map({ title: .title, number: .number, labels: .labels | length }) | 
   map(select(.labels > 0))'
[
  {
    "title": "Bump lxml from 4.3.1 to 4.6.3 in /docs",
    "number": 2295,
    "labels": 1
  },
  {
    "title": "Bump pyyaml from 3.13 to 5.4 in /docs",
    "number": 2291,
    "labels": 1
  },
  {
    "title": "Bump jinja2 from 2.10 to 2.11.3 in /docs",
    "number": 2289,
    "labels": 1
  },
  {
    "title": "Debugging help through showing pipeline intermediates. ",
    "number": 2206,
    "labels": 1
  }
]

这使用了三个对象索引、两个映射、两个管道、一个length函数和一个select谓词。但如果你一直跟着,这一切都应该是有道理的。这只是将过滤器组合在一起,直到获得所需的结果。

现在让我们谈谈如何将这些知识付诸实践。

审查中

我学到的是

以下是我到目前为止所学到的:

jq允许您通过以 a 开头.并访问键和数组来选择元素,就像它是一个 JavaScript 对象(就是这样)。此功能使用jqJSON 文档的对象和数组索引创建,如下所示:

jq '.key[0].subkey[2:3].subsubkey'

jq程序可以包含对象构造函数{ ... }和数组构造函数[ ... ]。当您想使用上述索引打包从 JSON 文档中提取的内容时,可以使用这些:

jq '[ { key1: .key1, key2: .key2 }  ]'

jq包含内置函数 ( length, sort, select, map) 和管道 ( |),您可以将它们组合在一起,就像在命令行中组合管道和过滤器一样:

  jq 'map({ order-of-magitude: .items | length | tostring | length }) 

掌握的后续步骤 jq

阅读(或撰写)一个工具并不足以掌握它。需要采取行动。这是我巩固这些知识的过程:

1. 完成jq教程

jq-tutorial根本不是教程,而是大约 20 个交互式练习的集合,可以测试您对jq. 我发现它非常有帮助。

2. 先尝试使用你的记忆

每当我需要提取数据或转换 JSON 文档时,我都会尝试先做,而无需查找任何内容。如果我记不住,有时具有自动完成功能的jqterm会有所帮助。通常,我仍然需要查找某些内容,但科学表明,重复检索会产生保留。所以随着时间的推移,我的保留率应该会提高。

3. 使用它

如果你不使用工具,你永远不会掌握它。因此,当我有一个可以使用 解决的任务时,jq这就是我使用的。至少在接下来的一段时间内,即使有更简单的方法。无论是探索 REST API 还是查看docker inspect结果,JSON 无处不在,因此机会比比皆是。

4. 了解更多

最后,为了加深我的知识,我正在学习递归下降、声明变量以及定义函数和手册中的高级功能。当然,这些东西很少出现,但是写完这些之后,我就迷上了这个工具。

做所有这些并不是必需的,但是如果您按照我的步骤进行其中一些步骤,我认为使用jq将成为第二天性。

结论

到目前为止,我只介绍了jq. 该jq查询语言是一个完整的编程语言,你可以做很多与它令人兴奋的事情。您从JSON转换为 CSV。你可以定义你自己的功能,甚至找到素数jq

# Denoting the input by $n, which is assumed to be a positive integer,
# eratosthenes/0 produces an array of primes less than or equal to $n:
def eratosthenes:
  (. + 1) as $n
  | (($n|sqrt) / 2) as $s
  | [null, null, range(2; $n)]
  | reduce (2, 1 + (2 * range(1; $s))) as $i (.; erase($i))
  | map(select(.));

然而,简单的东西——比如选择元素、按键或值过滤——通常是你所需要的。

我希望这有助于使您jq更平易近人,并且您不再需要每次要查询 JSON 文档时都去 google 7

当你在这里时:

Earthly是用于定义构建的语法。它适用于您现有的构建系统。立即获得可重复且可理解的构建。