jq工具的使用
jq 命令行行为的完整规则
命令格式 | 行为 | 示例 | 结果 |
---|---|---|---|
jq (无任何参数) |
显示帮助信息 | jq |
显示帮助文档 |
jq <选项> (无过滤器) |
显示帮助信息 | jq -c jq -n |
显示帮助文档 |
jq <过滤器> |
阻塞等待输入 | jq '.' jq 'map(.id)' |
等待 STDIN |
jq <选项> <过滤器> |
取决于选项: - 普通选项:等待输入 - -n :不等待 |
jq -c '.' → 等待jq -n '.' → 立即执行 |
|
jq <过滤器> 文件 |
立即处理文件 | jq '.' data.json |
处理文件内容 |
jq <选项> <过滤器> 文件 |
立即处理文件 | jq -c '.' data.json |
处理文件内容 |
1. 只有选项 → 显示帮助
$ jq -c
jq - commandline JSON processor [version 1.6]
Usage: jq [options] <jq filter> [file...]
...
# 立即显示帮助文档,不阻塞
2. 只有过滤器 → 阻塞等待
$ jq '.name'
(光标闪烁,程序挂起等待输入)
# 需要按 Ctrl+D 结束输入或 Ctrl+C 终止
3. 过滤器 + -n
→ 立即执行
$ jq -n '.name'
null # 立即输出结果(因为没有输入)
4. 过滤器 + 文件 → 立即处理
$ jq '.name' data.json
"Alice" # 立即从文件处理
为什么这个区别很重要?
在实际脚本编写中:
#!/bin/bash
# 危险:会阻塞脚本执行!
result=$(jq '.data')
# 安全:立即生成数据
result=$(jq -n '{time: now}')
# 安全:从变量读取
json='{"value": 42}'
result=$(jq '.value' <<< "$json")
基本操作:
jq [options] <jq filter> [file...]
[options]
- 可选选项
-
-r
:原始输出模式 -
-c
:紧凑输出 -
-n
:无输入模式
<jq filter>
- 必填过滤器
:用单引号包裹的表达式 '...'
-
'.'
:恒等过滤器 -
'.name'
:字段选择器 -
'map(.id)'
:数组处理
[file...]
- 可选输入文件
-
格式:文件路径(一个或多个)
-
-
data.json
:单个文件 -
file1.json file2.json
:多个文件
-
输入jq .后阻塞,然后我键盘输入a,报parse error: invalid numeric literal at line2,column 0,为啥报这个错误
-
jq 进入等待输入状态(因为没有使用
-n
选项且没有提供文件) -
光标在终端闪烁,等待您输入 JSON 数据
jq 要求输入必须是有效的 JSON,而 a
不是合法的 JSON。有效的 JSON 值必须是:
-
字符串:
"a"
(必须用双引号包裹) -
数字:
123
-
布尔值:
true
或false
-
对象:
{"key": "value"}
-
数组:
[1, 2, 3]
-
null:
null
虽然您输入的是字母,但 jq 的解析器是这样工作的:
-
首先尝试解析为数字(因为数字是 JSON 的基本类型)
-
当看到
a
时,它可能期待类似0xA
的十六进制数字 -
发现后面没有有效数字字符时抛出错误
1. JSON 格式要求 jq 要求输入必须是有效的 JSON,而 a 不是合法的 JSON。有效的 JSON 值必须是: 字符串:"a"(必须用双引号包裹) 数字:123 布尔值:true 或 false 对象:{"key": "value"} 数组:[1, 2, 3] null:null 不对吧,json不是key,value么,只输入上面的值也不是json啊?
真正的JSON格式要求:
-
顶层必须是以下之一:
-
对象(用
{}
包裹) -
数组(用
[]
包裹)
-
-
值类型:
-
字符串:必须双引号包裹
"text"
-
数字:整数或浮点数
42
,3.14
-
布尔值:
true
或false
-
空值:
null
-
对象:
{"key": "value"}
-
数组:
[1, 2, 3]
-
然JSON标准要求顶层必须是对象或数组,但jq对输入的处理更灵活:
-
jq接受任何有效的JSON值作为输入,包括:
-
简单值:
"string"
,42
,true
,null
-
复合值:
{...}
,[...]
-
-
这是jq的特性,不是JSON标准的特性:
# 这些在jq中都是有效的输入 echo '"text"' | jq . # 字符串 echo '42' | jq . # 数字 echo 'true' | jq . # 布尔值
为什么jq允许简单值?
这是jq设计者的选择:
-
实用主义:很多场景只需要处理简单值
-
管道友好:便于在命令行中处理各种数据
-
符合脚本需求:例如
jq -n '1 + 1'
直接计算
标准JSON vs jq输入对比
类型 | 标准JSON是否有效 | jq是否接受作为输入 |
---|---|---|
{"key": "value"} |
✅ | ✅ |
["a", "b"] |
✅ | ✅ |
"string" |
❌(顶层无效) | ✅ |
42 |
❌(顶层无效) | ✅ |
true |
❌(顶层无效) | ✅ |
null |
❌(顶层无效) | ✅ |
a |
❌ | ❌ |
json的key必须是字符串,value是上面几种数据类型?
JSON 格式的完整规范
-
顶层结构:
-
必须是对象(
{}
)或数组([]
) -
不能是简单值(字符串、数字、布尔值或 null)
-
-
键(Key)要求:
-
必须是字符串
-
必须用双引号包裹
-
不能是数字、布尔值或其他类型
// 正确 {"name": "Alice"} // 错误 - 键未加引号 {name: "Alice"} // 错误 - 键是数字 {42: "answer"}
-
-
值(Value)类型:
类型 示例 说明 字符串 "text"
必须双引号包裹 数字 42
,3.14
整数或浮点数 布尔值 true
,false
必须小写 数组 [1, "two", true]
有序值集合 对象 {"key": "value"}
键值对集合 null null
表示空值
虽然标准 JSON 要求顶层必须是对象或数组,但 jq 放宽了这个限制:
-
jq 接受简单值作为输入:
# 这些在 jq 中有效但不符合 JSON 标准 echo '"text"' | jq . # 字符串 echo '42' | jq . # 数字 echo 'true' | jq . # 布尔值
-
但键仍然必须遵循 JSON 规范:
# 尝试使用无效键 echo '{name: "Alice"}' | jq . # 报错:parse error: Invalid numeric literal at line 1 # 正确写法 echo '{"name": "Alice"}' | jq .
# 查看特定主机的 facts
ansible 172.17.79.16 -i hosts -m setup -o 2>/dev/null | awk-F '=> ''{print $2}'| jq .
或者ansible 172.17.79.16 -i hosts -m setup -o 2>/dev/null | tr -d "172.17.79.16 | success => "| less
-
对于数组:使用索引访问
.[0]
,.[1]
-
对于对象:使用键名访问
.["key"]
或.key
-
keys
函数适用于两种结构:-
数组 → 返回索引
-
对象 → 返回键名
-
-
组合使用管道
|
可以构建复杂查询
这两种访问方式是 jq 处理 JSON 的核心功能,掌握它们就能高效提取和转换 JSON 数据
the simplest filter is .,which is the identity filter
在jq工具中,.
(点号)是最简单的过滤器,称为"identity filter"(恒等过滤器)。它的作用是将输入的JSON数据原样输出,仅对格式进行标准化处理(如美化缩进)
identity(恒等)这一术语源自数学和计算机科学中的概念,指代一种不改变输入数据的操作,词根"ident"源于拉丁语"idem"(相同)48,后缀"-ity"表示性质或状态,组合后表示"同一性"或"不变性",如同身份证(identity card)唯一对应个人身份,区别于动词"identify"(识别)的动态过程,此处强调静态的"同一性"
例1:
jq '.[0] | keys' array_of_objects.json
array_of_objects.json
内容:
[
{"id": 1, "name": "Alice", "active": true},
{"id": 2, "email": "bob@example.com"},
{"role": "admin", "permissions": ["read", "write"]}
]
-
.[0]
获取数组的第一个元素{"id":1,"name":"Alice","active":true}
-
keys
提取这个对象的所有键名(按字母排序)
例2:.[]在数组和字典(对象)中的展开不同
对象的展开:
a.json的内容如下:
{ "id": 1, "name": "Alice", "roles": ["admin", "user"] }
命令jq '.[] | keys' a.json:
'.[]' a.json 展开的字典的value
结果:
1
Alice
["admin", "user"]
{"user": {"name": "Alice", "age": 30}, "server": {"ip": "1.1.1.1"}}
jq '.[] | keys' file.json
输出:
["age","name"]
["ip"]
数组的展开:
假设你的file.json:
[
{
"id": 1,
"name": "Alice",
"roles": ["admin", "user"]
},
{
"id": 2,
"name": "Bob",
"email": "bob@example.com"
},
{
"company": "Tech Inc",
"country": "USA"
}
]
-
.[]
:展开数组中的所有元素{"id":1,"name":"Alice","roles":["admin","user"]} {"id":2,"name":"Bob","email":"bob@example.com"} {"company":"Tech Inc","country":"USA"}
-
| keys
:对每个展开的对象获取键名["id","name","roles"] ["id","name","email"] ["company","country"]
最终输出:
[
"id",
"name",
"roles"
]
[
"id",
"name",
"email"
]
[
"company",
"country"
]
.[]
- 数组展开运算符
-
作用:将数组拆分为独立的元素
-
输入:
[元素1, 元素2, ...]
-
输出:
元素1
元素2
...(多个独立 JSON 对象) -
如果输入是对象:
{"a":1, "b":2}
→ 输出值1
2
jq '.object.nested_array[].property' file.json
.object # 第一层:访问根对象的 "object" 属性
.nested_array # 第二层:访问 object 对象的 "nested_array" 属性
[] # 解包操作:将 nested_array 数组展开为多个元素
.property # 第三层:访问每个数组元素的 "property" 属性
假设你的 file.json 内容 { "object": { "nested_array": [ { "id": 1, "property": "value1", "details": {"sub": "a"} }, { "id": 2, "property": "value2", "details": {"sub": "b"} } ], "other_field": "ignored" }, "unrelated": "data" }
运行命令:
jq '.object.nested_array[].property' file.json
输出:
"value1"
"value2"
关键概念解释
-
.
(根选择器)-
起始点,表示整个 JSON 文档的根
-
类似文件系统的
/
根目录
-
-
.object
(属性访问)-
访问根对象的
object
属性 -
等价于 JavaScript 的
data.object
-
-
.nested_array
(嵌套访问)-
访问
object
对象的nested_array
属性 -
类似
data.object.nested_array
-
-
[]
(数组迭代器)-
最关键的操作符:将数组展开为独立的元素
-
输入:
[ {...}, {...} ]
输出:{...}
和{...}
(两个独立对象)
-
-
.property
(叶节点访问)-
从每个展开的数组元素中提取
property
值 -
对每个元素执行:
element.property
-
类比其他语言
jq 表达式 | JavaScript 等价 | Python 等价 |
---|---|---|
.object |
data.object |
data['object'] |
.nested_array[] |
data.object.nested_array.forEach(...) |
for item in data['object']['nested_array']: |
.property |
item.property |
item['property'] |
# 查看特定主机的 facts
ansible 172.17.79.16 -i hosts -m setup -o 2>/dev/null | awk-F '=> ''{print $2}'| jq .
或者ansible 172.17.79.16 -i hosts -m setup -o 2>/dev/null | tr -d "172.17.79.16 | success => "| less
如果ansible输出的不是单行(比如没有使用`-o`选项),那么上述命令可能会失败,因为ansible默认输出的JSON是带缩进的多行格式,
而awk是按行处理的,这样每行都会被分割,导致输出多行内容给jq,而jq期望输入是一个完整的JSON文档。
-
ansible ... -o
-o
参数使输出为单行(关键!)172.17.79.16 | SUCCESS => {JSON数据}
-
awk -F'=> ' '{print $2}'
以"=> "
为分隔符,提取第二部分(纯JSON) -
jq .
美化输出 JSON
shell中的数组arr=(a b c d)
echo ${a[0]}
和
declare -A arr1=([0]=a [1]=b [2]=c [3]=d)
echo ${b[0]}
结果一样,有啥区别,难道普通数组可以看作关联数组的特殊数组?
是的,索引数组可以看作关联数组的特殊形式(键为连续整数),但在 Shell 实现中它们是不同的数据结构:
-
索引数组:为顺序访问优化的数组
-
关联数组:通用的键值存储
在您的例子中,当使用连续整数作为关联数组的键时,行为确实类似于索引数组,但底层实现和特性仍有差异。
因X.Y和X["Y"]都行,所以
ansible 172.17.69.16 -i hosts -m setup --key-file=./ssh/id_rsa -o 2>/dev/null | \
awk -F "=> " '{print $2}'|jq '.ansible_facts.ansible_nodename'
等价于
ansible 172.17.69.16 -i hosts -m setup --key-file=./ssh/id_rsa -o 2>/dev/null | \
awk -F "=> " '{print $2}'|jq '.["ansible_facts"]["ansible_nodename"]'
.是文本对象
查看ansible_facts所有key
ansible 172.17.69.16 -i hosts -m setup --key-file=./ssh/id_rsa -o 2>/dev/null | \
awk -F "=> " '{print $2}'|jq '.[]'
ansible 172.17.69.16 -i hosts -m setup --key-file=./ssh/id_rsa -o 2>/dev/null | \
awk -F "=> " '{print $2}'|jq '.ansible_facts|keys'