JQ简介
背景:手指、头部和谷歌
每当我到达工作的停止点时,我都会使用名为gwip
1的 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功能等内置插件可供选择,喜欢length
,reverse
和tostring
他们都可以以类似的方式使用:
$ 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
是一个内置函数,它接受一个布尔表达式并且只返回匹配的元素。它类似于WHERE
SQL 语句中的子句或 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 对象(就是这样)。此功能使用jq
JSON 文档的对象和数组索引创建,如下所示:
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是用于定义构建的语法。它适用于您现有的构建系统。立即获得可重复且可理解的构建。