jq JSON 处理工具完整指南
jq 学习笔记 -- JSON 命令行处理利器
一、jq 概述与基本概念
1.1 什么是 jq
jq 是一个轻量级、灵活的命令行 JSON 处理工具。它就像 JSON 数据的 grep/sed/awk,让你能够以极其简洁的方式解析、过滤、映射和转换 JSON 数据。jq 使用一种强大的领域特定语言(DSL),通过管道和过滤器组合的方式操作 JSON 数据,支持复杂的查询、变换和格式化输出。
jq 的设计哲学
jq 的设计哲学与 Unix 管道哲学一脉相承:每个过滤器做一件事,通过管道(|)组合成强大的数据处理流水线。jq 的输入是 JSON 数据流,输出也是 JSON 数据流,这使得它可以无缝嵌入到任何 Shell 管道链中。一个典型的 jq 命令就像是一个声明式的数据转换描述:你描述"想要什么",而不是"如何获取"。
1.2 为什么需要 jq
在现代开发和运维中,JSON 已经成为最通用的数据交换格式。API 响应、配置文件、日志记录、数据库导出等场景无处不在。手动使用 grep、sed、awk 等传统文本工具处理结构化 JSON 数据存在诸多痛点:
| 痛点 | 传统文本工具的问题 | jq 的解决方案 |
| 嵌套结构 | grep/sed 无法理解 JSON 的嵌套层级关系 | jq 原生支持通过 . 运算符访问嵌套属性 |
| 数组索引 | awk 很难提取数组中的特定元素 | jq 支持 .[], .[index], .[start:end] 等数组操作 |
| 格式化输出 | 压缩的 JSON 一行难以阅读 | jq 默认输出美化格式,也可以输出紧凑格式 |
| 条件过滤 | 需要在管道中组合多个命令实现过滤 | jq 的 select() 函数一行实现条件过滤 |
| 数据转换 | 复杂的 Shell 脚本才能完成数据重塑 | jq 通过管道组合过滤器轻松完成数据映射和转换 |
| 键值操作 | 提取/修改/删除 JSON key 需要复杂操作 | jq 支持 del(), map(), with_entries() 等 |
核心价值:jq 将 JSON 从原始文本提升为可编程操作的结构化数据。它的学习曲线虽然比 grep 陡峭,但一旦掌握,处理 JSON 的效率可以提升 10 倍以上。对于日常使用 API、处理配置文件、分析日志的开发者来说,jq 是不可或缺的生产力工具。
1.3 jq 的工作模式
jq 的核心工作模式可以概括为三步:
- 输入:读取 JSON 数据(从标准输入或文件)
- 处理:应用过滤器(filter)对数据进行变换
- 输出:输出处理后的 JSON 数据
jq '过滤器' input.json
cat input.json | jq '过滤器'
curl -s https://api.example.com/data | jq '.items'
JSON 输入
→
jq 过滤器
→
JSON 输出
1.4 标识符:. 的含义
在 jq 中,. 是最基本的过滤器,代表"当前输入"或"整个文档"。它类似于面向对象语言中的 this 或 self。单独使用 . 时,只是将输入原样输出(通常会对输入进行格式化美化和着色)。
echo '{"name":"Alice","age":30}' | jq '.'
{
"name": "Alice",
"age": 30
}
颜色输出
jq 默认开启彩色输出:键名为蓝色、字符串为绿色、数字为紫色、布尔值为黄色、null 值为灰色。这在终端中阅读时非常清晰。如果要禁用颜色,可以使用 --monochrome-output 或 -M 选项。
二、安装与配置
2.1 各平台安装方式
jq 使用 C 语言编写,无运行时依赖,单个二进制文件即可运行。以下是在各主流平台上的安装方法:
| 平台 | 安装命令 | 说明 |
| Linux (Debian/Ubuntu) | sudo apt install jq | 官方仓库通常提供稳定版本 |
| Linux (RHEL/CentOS/Fedora) | sudo yum install jq 或 sudo dnf install jq | EPEL 仓库可能需额外启用 |
| Linux (Arch) | sudo pacman -S jq | Arch 社区仓库维护 |
| macOS (Homebrew) | brew install jq | 最推荐的方式,版本较新 |
| macOS (MacPorts) | port install jq | MacPorts 用户的选择 |
| Windows (Chocolatey) | choco install jq | Windows 包管理器 |
| Windows (Scoop) | scoop install jq | Scoop 用户的选择 |
| Windows (WSL) | sudo apt install jq (WSL 环境内) | 在 WSL Linux 子系统中安装 |
| Windows (二进制下载) | 从 jq 官网下载 jq.exe 并加入 PATH | 无依赖的单个 exe 文件 |
| Docker | docker pull stedolan/jq | 容器化运行 |
| Go (源码编译) | go install github.com/itchyny/gojq/cmd/gojq@latest | Go 语言实现的 jq 替代品 |
jq --version
jq-1.7.1
curl -L -o /usr/local/bin/jq https://github.com/jqlang/jq/releases/latest/download/jq-linux-amd64
chmod +x /usr/local/bin/jq
2.2 版本选择
| 版本 | 特点 | 建议 |
| jq 1.6 (2018) | 广泛使用的稳定版本,多数系统默认 | 兼容性好,功能完整 |
| jq 1.7 (2023) | 最新稳定版,新增 --raw-output 改进等 | 推荐升级,向下兼容 |
| gojq (第三方) | Go 语言实现,支持 yq 格式互转 | 需要多格式支持时选用 |
推荐版本
建议始终使用 jq 1.7 或更新版本。jq 1.7 修复了 1.6 中的多个 bug,提升了性能,并且完全向后兼容。你可以通过 jq --version 检查当前版本。如果你的系统自带版本较旧,建议从官方 GitHub Releases 页面下载最新版本。
2.3 基本使用选项
| 选项 | 简写 | 说明 |
--raw-output | -r | 输出原始字符串(不带 JSON 引号) |
--raw-input | -R | 将输入视为原始字符串而非 JSON |
--null-input | -n | 不读取输入,使用 null 作为输入值 |
--compact-output | -c | 紧凑输出(每行一个 JSON 对象) |
--color-output | -C | 强制彩色输出(即使不是 tty) |
--monochrome-output | -M | 禁用彩色输出 |
--arg | | 将 Shell 变量传入 jq 过滤器 |
--argjson | | 将 JSON 值传入 jq 过滤器 |
--slurp | -s | 将输入流读入数组后再处理 |
--from-file | -f | 从文件读取过滤器 |
echo '{"name":"Alice"}' | jq -r '.name'
echo '{"a":1,"b":2}' | jq -c '.'
jq -n '{name: "test", value: 42}'
jq -s '.' file1.json file2.json
三、基本查询语法
3.1 最简单的查询:属性访问
使用点号(.)加属性名即可访问 JSON 对象的键值。这是 jq 最基础和常用的操作。
{
"name": "Alice",
"age": 30,
"city": "Shanghai",
"job": {
"title": "Engineer",
"company": "Tech Corp",
"salary": 50000
},
"skills": ["Python", "Go", "JavaScript"],
"active": true
}
jq '.name' data.json
jq '.age' data.json
jq '.city' data.json
jq '.active' data.json
jq '.job.title' data.json
jq '.job.company' data.json
jq '.job.salary' data.json
jq '.job | .title' data.json
3.2 可选操作符 ?
当访问可能不存在的属性时,可以使用 ? 操作符避免错误。这在处理动态结构或异构数据时非常有用。
jq '.nonexistent' data.json
jq '.job.nonexistent.field' data.json
jq 'try .job.nonexistent.field catch null' data.json
3.3 多个属性查询
使用逗号 , 分隔多个过滤器,可以同时获取多个属性的值。
jq '.name, .age, .city' data.json
jq '{name: .name, age: .age}' data.json
jq '{name, age, city}' data.json
jq '{员工姓名: .name, 职位: .job.title, 公司: .job.company}' data.json
, 与 {} 的区别
使用逗号 , 分隔时,每个过滤器产生独立的输出流(多行输出)。使用花括号 {} 时,将所有字段组合成一个 JSON 对象输出。, 适用于需要逐个处理多个值的场景,{} 适用于需要结构化输出的场景。
3.4 输入输出格式
jq 可以处理两种输入模式:单个 JSON 对象和 JSON Lines(每行一个 JSON 对象)。输出也可以是美化格式或紧凑格式。
cat << 'EOF' | jq -c '.name'
{"name": "Alice", "id": 1}
{"name": "Bob", "id": 2}
{"name": "Charlie", "id": 3}
EOF
jq -c '{name, id}' data.json
JSON Lines (NDJSON)
JSON Lines(也称 NDJSON —— Newline Delimited JSON)是一种将多个 JSON 对象放在同一文件中、每行一个的格式。这是很多日志系统和流处理系统的标准输出格式。jq 的 -c 选项正好产生这种格式,非常适合管道链中的进一步处理。
四、过滤器操作详解
4.1 管道过滤器 |
jq 中的管道操作符 | 与 Shell 中的管道概念完全一致:将左侧过滤器的输出作为右侧过滤器的输入。这是构建复杂查询的基础。
jq '.job | .title' data.json
jq '.job | {title, salary}' data.json
jq '.skills | .[] | ascii_upcase' data.json
理解 jq 管道:jq 中的管道与 Shell 的管道非常相似。每个过滤器接收一个输入值,产生零个或多个输出值。管道将左侧的输出逐个传递给右侧的过滤器。这意味着如果左侧产生了 N 个结果,右侧过滤器会被执行 N 次(每次接收一个输入值)。这种"流式"处理模型是 jq 强大和高效的根源。
4.2 标识过滤器 . 和 ..
. 代表当前节点的值。.. 是递归下降操作符,递归遍历 JSON 结构中的所有节点。
jq '..' data.json
jq '.. | strings' data.json
jq '.. | numbers' data.json
jq '.. | .name? // empty' data.json
jq '.. | booleans' data.json
4.3 类型过滤器
jq 提供了类型过滤器,用于根据值的类型进行过滤。
| 过滤器 | 描述 | 示例 |
strings | 只保留字符串值 | .[] | strings |
numbers | 只保留数字值 | .[] | numbers |
booleans | 只保留布尔值 | .[] | booleans |
nulls | 只保留 null 值 | .[] | nulls |
arrays | 只保留数组 | .[] | arrays |
objects | 只保留对象 | .[] | objects |
iterables | 保留数组和对象 | .[] | iterables |
scalars | 保留标量值(非数组非对象) | .[] | scalars |
jq -n '[1, "hello", null, true, {}, []] | .[] | type'
jq -n '[1, "hello", null, true, {}, []] | .[] | scalars'
4.4 表达式过滤器 ? 和 //
jq 提供了错误抑制操作符 ? 和默认值操作符 //,用于处理缺失值或错误。
echo '{"name": "Alice"}' | jq '.age // 0'
echo '{"name": "Alice", "age": 25}' | jq '.age // 0'
jq '(.job.salary // 0) * 12' data.json
jq 'try .job.nonexistent catch empty' data.json
五、数组与对象处理
5.1 数组迭代 .[]
.[] 是数组迭代操作符,它将数组中的每个元素展开为独立的输出值。这是 jq 中最常用的操作符之一。
echo '{"items": ["apple", "banana", "cherry"]}' | jq '.items[]'
echo '[{"name":"Alice","age":30},{"name":"Bob","age":25},{"name":"Charlie","age":35}]' | jq '.[]'
echo '[{"name":"Alice","age":30},{"name":"Bob","age":25}]' | jq '.[] | .name'
echo '[{"name":"Alice","age":30},{"name":"Bob","age":25}]' | jq '.[] | {name, age}'
5.2 数组切片 .[start:end]
jq 支持与 Python 一致的数组切片语法,可以快速获取数组的子集。
jq -n '[10, 20, 30, 40, 50] | .[0:2]'
jq -n '[10, 20, 30, 40, 50] | .[2:]'
jq -n '[10, 20, 30, 40, 50] | .[:-2]'
jq -n '[10, 20, 30, 40, 50] | .[-2:]'
jq -n '[10, 20, 30, 40, 50] | .[1:4]'
切片规则
.[m:n] -- 从索引 m 到 n(不包含 n),与 Python 一致
.[n:] -- 从索引 n 到末尾
.[:n] -- 从开头到索引 n(不包含 n)
.[-n:] -- 最后 n 个元素
.[:-n] -- 去掉最后 n 个元素后的部分
5.3 数组构造 []
使用方括号可以将多个输出值收集为数组。
jq '[.name, .age, .city]' data.json
echo '[{"name":"Alice"},{"name":"Bob"},{"name":"Charlie"}]' | jq '[.[] | .name]'
echo '[{"name":"Alice"},{"name":"Bob"},{"name":"Charlie"}]' | jq 'map(.name)'
5.4 对象构造与解构
jq 提供了灵活的对象构造语法,可以轻松创建新对象或重塑现有对象。
jq '{full_name: .name, age_years: .age, job_title: .job.title}' data.json
jq '{name, age, job}' data.json
jq '. + {annual_salary: (.job.salary * 12)}' data.json
jq -n '{a: 1, b: 2} + {b: 3, c: 4}'
jq '{name, (": " + (.age | tostring)): .age}' data.json
5.5 删除字段 del()
jq 'del(.job.salary)' data.json
jq 'del(.job, .skills)' data.json
jq 'with_entries(select(.value != null and .value != ""))' data.json
5.6 对象字段迭代 to_entries / from_entries / with_entries
这三个函数是处理对象键值对的瑞士军刀。它们将对象转换为键值对数组,操作后再转回对象。
jq -n '{a: 1, b: 2, c: 3} | to_entries'
jq -n '[{"key":"a","value":1},{"key":"b","value":2}] | from_entries'
jq -n '{a: 1, b: 2, c: 3, d: 4} | with_entries(select(.value > 1))'
jq -n '{a: 1, b: 2, c: 3} | with_entries(.value *= 2)'
jq -n '{old_name: 1} | with_entries(.key = "new_name")'
六、管道与函数
6.1 数学函数
jq 内置了丰富的数学函数,支持对数值进行各种运算。
jq -n '[1, 2, 3, 4, 5] | add'
jq -n '[1, 2, 3, 4, 5] | length'
jq -n '[1, 2, 3, 4, 5] | min'
jq -n '[1, 2, 3, 4, 5] | max'
jq -n '[1, 2, 3, 4, 5] | min_by(.)'
jq -n '[1, 2, 3, 4, 5] | max_by(.)'
jq -n '[1, 2, 3, 4, 5] | sort'
jq -n '[1, 2, 3, 4, 5] | reverse'
jq -n '[1, 2, 3, 4, 5] | average'
6.2 字符串函数
jq -n '"hello" + " " + "world"'
jq -n '42 | tostring'
jq -n '"42" | tonumber'
jq -n '"hello" | length'
jq -n '"hello" | ascii_upcase'
jq -n '"HELLO" | ascii_downcase'
jq -n '" hello " | ltrimstr(" ")'
jq -n '"hello\nworld"'
jq -n '"a,b,c" | split(",")'
jq -n '["a","b","c"] | join(",")'
jq -n '"hello"[0:2]'
jq -n '"hello"[2:]'
jq -n '"hello"[:4]'
jq -n '"hello world" | contains("world")'
jq -n '"hello" | startswith("he")'
jq -n '"hello" | endswith("lo")'
jq -n '"hello" | index("el")'
6.3 数组函数
jq -n '[1, 2, 3, 4] | map(. * 2)'
jq -n '[1, 2, 3, 4] | map(select(. > 2))'
jq -n '{a: 1, b: 2, c: 3} | map_values(. * 2)'
jq -n '[3, 1, 2, 1, 3, 2] | unique'
jq -n '[{"city":"SH","val":1},{"city":"BJ","val":2},{"city":"SH","val":3}] | group_by(.city)'
jq -n '[[1,2],[3,[4,5]]] | flatten'
jq -n '[[1,2],[3,[4,5]]] | flatten(1)'
map 与 .[] 的区别:map(f) 等价于 [.[] | f]。前者保持数组结构,后者展开为独立值。当需要保留数组结构时使用 map,当需要逐个处理时使用 .[]。这是一个重要的概念区别。
6.4 选择函数 select()
select() 是 jq 中最常用的条件过滤函数。它接收一个条件表达式,只保留满足条件的值。
echo '[{"name":"Alice","age":30,"salary":50000},{"name":"Bob","age":25,"salary":40000},{"name":"Charlie","age":35,"salary":60000}]' | \
jq '.[] | select(.age > 28)'
jq '.[] | select(.age > 25 and .salary > 45000)'
jq '.[] | select(.name | startswith("A"))'
jq '.[] | select(.name | contains("li"))'
jq '.[] | select(.name | test("^[AB]"))'
jq '.[] | select(has("email"))'
jq '.[] | select(.age < 30 | not)'
6.5 排序函数 sort_by / group_by
jq 'sort_by(.age)' users.json
jq 'sort_by(-.age)' users.json
jq 'sort_by(.city, .name)' users.json
jq 'group_by(.dept) | map({dept: .[0].dept, count: length, total_salary: map(.salary) | add})' employees.json
七、条件与迭代
7.1 if-then-else
jq 支持完整的条件表达式语法。
jq 'if .age >= 18 then "adult" else "minor" end' data.json
jq 'if .age < 18 then "minor" elif .age < 60 then "adult" else "senior" end' data.json
echo '[25, 16, 35, 12, 60]' | jq 'map(if . >= 18 then "Y" else "N" end)'
echo '[1,2,3,4,5]' | jq '.[] | select(if . > 3 then true else false end)'
7.2 比较操作符
| 操作符 | 含义 | 示例 | 结果 |
== | 相等比较 | 1 == 1 | true |
!= | 不等比较 | 1 != 2 | true |
> | 大于 | 3 > 2 | true |
>= | 大于等于 | 3 >= 3 | true |
< | 小于 | 2 < 3 | true |
<= | 小于等于 | 2 <= 2 | true |
and | 逻辑与 | true and false | false |
or | 逻辑或 | true or false | true |
not | 逻辑非 | not true | false |
jq 'select(.age >= 20 and .age <= 40)'
jq 'select(.email != null)'
jq 'select(.role == "admin" or .role == "moderator")'
7.3 迭代模式详解
jq '[.[] | {name, age}]' users.json
jq '[.[] | select(.active == true) | {name}]' users.json
jq '[.[] | {name, age_group: if .age >= 60 then "senior" elif .age >= 18 then "adult" else "minor" end}]' users.json
jq '.[] | .orders[] | {user: .user, product: .product}' data.json
性能注意事项
在处理大型 JSON 文件(如数百 MB 的日志)时,jq 的流式处理模型是高效的。但要注意:map 和数组构造器 [] 会将整个结果加载到内存中。如果数据量极大,应该避免使用数组构造器,而是使用 .[] 逐行输出并在下游处理。
7.4 空值处理 empty
empty 和 null 是 jq 中两个重要的概念。empty 代表"没有输出"(零个结果),而 null 是一个 JSON 值。
jq -n 'null'
jq -n 'empty'
jq -n '[1, 2, 3, 4, 5] | .[] | if . > 3 then . else empty end'
jq -n '[1, 2, 3, 4, 5] | .[] | select(. > 3)'
八、字符串操作
8.1 字符串插值与格式化
jq '"User: " + .name + ", Age: " + (.age | tostring)' data.json
jq '"User: \(.name), Age: \(.age)"' data.json
jq '"\(.name) works as a \(.job.title) at \(.job.company)"' data.json
字符串插值 vs 拼接
使用 \(expression) 语法(jq 1.7+ 支持)比使用 + 拼接更简洁,且会自动将值转换为字符串,无需手动调用 tostring。推荐在所有字符串格式化场景中使用插值语法。
8.2 正则表达式
jq 提供了完整的正则表达式支持,使用 PCRE(Perl Compatible Regular Expressions)语法。
jq -n '"hello123" | test("^[a-z]+[0-9]+$")'
jq -n '"Hello123" | test("^[a-z]+[0-9]+$")'
jq -n '"Hello123" | test("^[a-z]+[0-9]+$"; "i")'
jq -n '"color: #ff8800" | match("#[0-9a-fA-F]+")'
jq -n '"2026-05-08" | capture("(?[0-9]{4})-(?[0-9]{2})-(?[0-9]{2})")'
jq '.[] | select(.email | test("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"))' users.json
jq -n '"hello world" | sub("world"; "jq")'
jq -n '"aaa bbb ccc" | gsub(" "; ",")'
jq -n '"hello123world" | gsub("[0-9]"; "_")'
8.3 分割与合并
jq -n '"a,b,c,d" | split(",")'
jq -n '"a|b|c" | split("[|]"; "g")'
jq -n '["a","b","c"] | join(", ")'
echo '"name,age,city" | split(",")' | jq -r '.[]'
echo '[{"name":"Alice","age":30},{"name":"Bob","age":25}]' | \
jq -r '.[] | [.name, (.age | tostring)] | @tsv'
8.4 格式化输出指示器
jq 提供了一组特殊的 @ 指令,用于将 JSON 数据转换为其他文本格式。
| 指令 | 描述 | 示例输入 | 输出 |
@text | 字符串文本输出 | "hello" | hello |
@json | JSON 输出 | {a:1} | {"a":1} |
@html | HTML 转义 | " " | <br> |
@uri | URI 编码 | "a b" | a%20b |
@csv | CSV 格式 | ["a","b"] | "a","b" |
@tsv | TSV 格式 | ["a","b"] | a\tb |
@sh | Shell 转义 | "a'b" | 'a'\''b' |
@base64 | Base64 编码 | "hello" | aGVsbG8= |
@base64d | Base64 解码 | "aGVsbG8=" | "hello" |
echo '[{"name":"Alice","age":30},{"name":"Bob","age":25}]' | \
jq -r '.[] | [.name, .age] | @csv'
jq -r '.[] | [.name, .city] | @sh' data.json
jq -n '"<script>alert(1)</script>" | @html'
@csv 和 @tsv 的注意点:这两个格式化指令要求输入必须是数组。如果输入不是数组,需要用 [...] 包装。配合 -r(raw output)使用才能得到正确的无引号输出。
九、高级特性(reduce、foreach、递归等)
9.1 reduce 函数
reduce 是 jq 中最强大的函数之一,它将数组中的元素逐个累积计算,最终产生一个单一值。这个概念类似于函数式编程中的 fold/reduce/inject。
jq -n 'reduce [1,2,3,4,5][] as $n (0; . + $n)'
jq -n 'reduce ["a","b","c"][] as $k ({}; .[$k] = 1)'
echo '[{"dept":"IT","name":"Alice","salary":5000},{"dept":"IT","name":"Bob","salary":4000},{"dept":"HR","name":"Charlie","salary":3000}]' | \
jq 'reduce .[] as $e ({}; .[$e.dept] += $e.salary)'
jq -n '[1,2,3,4,5] | reduce .[] as $n ({sum:0, product:1, count:0}; {sum: .sum + $n, product: .product * $n, count: .count + 1})'
reduce 执行流程
1. 初始值 0 作为第一个 . 进入累积表达式
2. 数组的第一个元素 $n=1,计算结果 0+1=1,成为新的 .
3. 第二个元素 $n=2,计算结果 1+2=3,成为新的 .
4. 以此类推直到所有元素处理完毕
5. 返回最终累积值
9.2 foreach 函数
foreach 类似于 reduce,但它在每次迭代时都会输出中间结果。这使得它非常适合处理需要累积上下文但又要输出每一步结果的场景。
jq -n '[1,2,3,4,5] | foreach .[] as $n (0; . + $n; {step: $n, running_total: .})'
jq -n '[1,2,3,4,5] | foreach .[] as $n (0; . + $n; .)'
9.3 递归 .. 和 recurse()
除了 .. 操作符外,jq 还提供了 recurse() 函数用于自定义递归。
jq '.. | select(. != null)' data.json
jq -n '{"a":{"b":{"c":1}}} | recurse | select(type == "object")'
jq -n '[1,[2,[3,[4]]]] | recurse(.[]?; . | length > 0)'
jq -n '{"a":{"b":1,"c":[{"d":2}]},"e":3} | walk(if type == "number" then . * 2 else . end)'
9.4 变量绑定 as
as 用于将表达式的结果绑定到变量,在后续过滤器中重复使用。
jq '.job.salary as $s | {name, annual_salary: ($s * 12)}' data.json
echo '["Alice", 30, "Shanghai"]' | jq '.[0] as $name | .[1] as $age | "\($name) is \($age) years old"'
jq '{name: $n, age: $a} = . | "\($n) is \($a)"' data.json
jq '.job.salary as $s | {name, monthly: $s, annual: ($s * 12), tax: ($s * 12 * 0.2)}' data.json
9.5 定义函数 def
jq 允许你定义自己的函数来封装重复逻辑。
jq -n 'def double: . * 2; [1,2,3] | map(double)'
jq -n 'def greet(name): "Hello, \(name)!"; greet("World")'
jq -n 'def count_by(f): reduce .[] as $x ({}; .[$x|f] += 1); ["a","b","a","c","b","b"] | count_by(.)'
模块系统注意事项
jq 1.7 引入了模块系统(include、module),但不同版本的 jq 对模块路径的解析规则可能有细微差异。在团队协作中,建议确保所有人使用相同的 jq 版本,或者将常用函数直接写在过滤器中而不是依赖模块文件。
9.6 输入函数 inputs
inputs 函数用于逐行读取输入流,与 -n 选项结合使用可以精细控制处理过程。
echo '{"id":1}
{"id":2}
{"id":3}' | jq -n 'inputs | .id'
echo '{"id":1}
{"id":2}
{"id":3}' | jq -n 'first(inputs) | .id'
jq -n 'foreach inputs as $item (0; . + 1; {index: ., value: $item})' < data.jsonl
inputs 与默认输入的区别
默认情况下(不加 -n),jq 将整个输入解析后传递给过滤器。使用 -n + inputs 时,jq 不预先读取输入,而是通过 inputs 函数惰性地逐行读取。这在处理超大文件时特别有用,可以减少内存占用。当需要对数据流进行细粒度控制时(如跳过前 N 行),inputs 是必要的工具。
十、实战应用场景
10.1 API 数据处理
在现代开发中,jq 最常见的用途之一是处理和转换 API 响应数据。无论是 REST API 还是 GraphQL 响应,jq 都能快速提取需要的信息。
curl -s https://api.github.com/repos/jqlang/jq | jq '{name, description, stars: .stargazers_count, forks, license: .license.spdx_id}'
curl -s 'https://api.github.com/repos/jqlang/jq/issues?state=open&per_page=5' | \
jq '.[] | {number, title, state, user: .user.login, comments}'
curl -s https://api.example.com/v2/users \
| jq '{total: .meta.total, users: [.data[] | {id, name, email, created_at: .created_at[0:10]}]}'
curl -s https://status.example.com/api/checks \
| jq '[.checks[] | {name, status, response_time: .response_time_ms}] | group_by(.status) | map({status: .[0].status, count: length})'
API 处理最佳实践:
- 先用
jq '.' 查看 API 响应的整体结构
- 逐步构建过滤器,从小处着手、逐步扩展
- 使用
--arg 将 Shell 变量传入过滤器,避免硬编码
- 对于分页响应,结合 Shell 循环和 jq 完成批量提取
10.2 日志分析
jq 'select(.level == "ERROR")' app.log
jq -n 'reduce inputs as $log ({}; .[$log.service] += 1)' < app.log
jq 'select(.duration_ms > 1000) | {timestamp, message, duration_ms}' app.log
jq -n '[inputs | select(.level == "ERROR") | .timestamp[0:13]] | group_by(.) | map({hour: .[0], count: length})' < app.log
jq -s 'sort_by(.duration_ms) | reverse[:10] | [.[] | {timestamp, service, duration_ms}]' app.log
jq -n 'reduce inputs as $log ({}; .[$log.status | tostring] += 1)' < access.log
jq -s 'group_by(.path) | map({path: .[0].path, avg_time: (map(.response_time) | add / length), count: length}) | sort_by(-.avg_time) | .[:10]' access.log
jq -n 'reduce inputs as $log ({}; .[$log.ip] += 1) | to_entries | sort_by(-.value) | .[:10]' < access.log
10.3 配置文件处理
jq 'has("database") and has("server") and .server.port > 0' config.json
jq -s '.[0] * .[1]' base.json override.json > merged.json
jq '.server.port = 8080 | .logging.level = "debug"' config.json > config.new.json
jq '.database = (.database | .host = $host | .password = $pwd)' \
--arg host "prod-db.example.com" \
--arg pwd "${DB_PASSWORD}" \
config.json > config.prod.json
jq '{module_name: .name, dependencies: .dependencies | keys, scripts: .scripts | to_entries | map("\(.key): \(.value)")}' package.json
10.4 数据迁移与转换
jq '{first_name: .name, age_years: .age, location: .city}' data.json
echo '{"user":{"name":"Alice","address":{"city":"SH","zip":"200000"}}}' | \
jq '{user_name: .user.name, user_city: .user.address.city, user_zip: .user.address.zip}'
echo '{"orders": [{"id":1,"items":["a","b"]},{"id":2,"items":["c"]}]}' | \
jq '[.orders[] | .items[] as $item | {order_id: .id, item: $item}]'
jq 'map(select(.email != null and .email | test("@"))) | map({name, email, active: (. active // false)})' users.json
jq -r '.[] | [.name, .age, .city] | @csv' data.json > data.csv
jq -r '.[] | "| \(.name) | \(.age) | \(.city) |"' data.json
10.5 与 curl 结合的最佳实践
curl -s -H "Authorization: Bearer $TOKEN" https://api.example.com/data \
| jq '.data[] | {id, name, status}'
payload=$(jq -n '{"name": "New Item", "price": 29.99, "category": "books"}')
curl -s -X POST -H "Content-Type: application/json" -d "$payload" https://api.example.com/items \
| jq '.'
item_id=$(curl -s https://api.example.com/items | jq -r '.items[0].id')
curl -s "https://api.example.com/items/$item_id/details" | jq '.'
for endpoint in users orders products; do
curl -s "https://api.example.com/$endpoint" | jq '{type: "$endpoint", count: (.meta.total // length)}'
done
10.6 Docker 和 Kubernetes 场景
docker inspect nginx | jq '.[] | {id: .Id[0:12], created: .Created, ports: .Config.ExposedPorts}'
docker stats --no-stream --format '{{json .}}' | jq -s '.[] | {name: .Name, cpu: .CPUPerc, mem: .MemPerc}'
kubectl get pods -o json | jq '.items[] | {name: .metadata.name, status: .status.phase, node: .spec.nodeName, ip: .status.podIP}'
kubectl get pods -o json | jq '[.items[] | {name: .metadata.name, containers: [.spec.containers[] | {name, image}]}]'
kubectl get deployment -o json | jq '.items[] | {name: .metadata.name, replicas: .spec.replicas, ready: .status.readyReplicas // 0, updated: .status.updatedReplicas // 0}'
十一、与 Claude Code 结合使用
11.1 在 Claude Code 中调用 jq
Claude Code 通过 Bash 工具执行命令,jq 是 Claude Code 中进行 JSON 数据处理的天然选择。以下是在 Claude Code 对话中常见的 jq 使用模式:
jq '.' package.json
jq '{dependencies: .dependencies | keys, devDependencies: .devDependencies | keys}' package.json
jq '{compilerOptions: .compilerOptions | {target, module, strict, outDir}}' tsconfig.json
jq '{total: .numTotalTests, passed: .numPassedTests, failed: .numFailedTests, time: .testResults[0].duration}' test-results.json
jq '{rules_count: (.rules | length), extends: .extends}' .eslintrc.json
11.2 与文件操作工具配合
find . -name "*.json" -not -path "*/node_modules/*" | xargs jq '.' 2>/dev/null | head -50
find . -name "*.json" -not -path "*/node_modules/*" -exec wc -c {} \; | sort -rn | head -10
for f in $(find . -name "*.json" -not -path "*/node_modules/*"); do
if jq -e '.dependencies | has("react")' "$f" >/dev/null 2>&1; then
echo "$f"
fi
done
11.3 在开发工作流中的典型应用
diff <(curl -s https://api-v1.example.com/users | jq '.[] | {id, name}') \
<(curl -s https://api-v2.example.com/users | jq '.[] | {id, name, email}')
jq '[.jobs[] | {name, stage, image}]' pipeline-report.json
jq 'has("$schema") and has("properties") and (.properties | length > 0)' schema.json
jq '[.outputs[] | {name: .name, size: .size, chunks: .chunks | length}] | sort_by(-.size)' build-stats.json
Claude Code + jq 的效率模式:在 Claude Code 的对话中,当你需要处理 JSON 数据时,可以直接用自然语言描述需求(如"查看这个 package.json 中所有依赖的名称和版本"),而不需要手动编写 jq 过滤器。Claude Code 会自动选择并构建合适的 jq 命令来完成你的需求。这极大降低了 jq 的使用门槛。
11.4 实用组合模式
for pkg in packages/*/package.json; do
jq -r 'select(.devDependencies.typescript != null) | .name' "$pkg"
done
for f in *.md; do
sed -n '/^---$/,/^---$/p' "$f" | sed '1d;$d' | jq '.' 2>/dev/null || true
done
for ep in users posts comments; do
(curl -s "https://api.example.com/$ep" | jq 'length') &
done
wait
十二、常见问题与排错
12.1 语法和解析错误
| 错误信息 | 原因 | 解决方案 |
parse error: Invalid numeric literal at line 1, column 2 | 输入不是合法的 JSON | 检查 JSON 格式(引号、逗号等),使用 jq . 验证 |
jq: error: syntax error, unexpected ... | 过滤器语法错误 | 检查引号、括号配对,注意转义字符 |
Cannot index array with string ... | 尝试用字符串索引数组 | 确认数据类型,使用 .[] 或数字索引 |
Cannot index object with number ... | 尝试用数字索引对象 | 确认数据类型,使用字符串键名 |
jq: error (at ...): number (N) cannot be iterated over | 对非数组值使用 .[] | 确认值为数组类型,或使用类型过滤器 |
jq '.' file.json > /dev/null 2>&1 || echo "无效的 JSON"
if jq -e '.' file.json >/dev/null 2>&1; then
echo "JSON 有效"
else
echo "JSON 无效"
fi
echo "{'key': 'value'}" | sed "s/'/\"/g" | jq '.'
echo '{"a":1,"b":2,}' | jq '.'
echo '{"a":1,"b":2,}' | python -c "import sys,json; print(json.dumps(json.load(sys.stdin)))" | jq '.'
Shell 引号陷阱
在 Shell 中编写 jq 过滤器时,引号是最容易出错的地方:
- 使用单引号包裹整个过滤器:
jq '.key' file(推荐,避免 Shell 展开)
- 过滤器中需要单引号时:使用双引号包裹整个过滤器,或转义
- 过滤器中需要双引号时:在单引号内直接使用即可
- Shell 变量传入:使用
--arg varname value,而非直接在过滤器中拼接变量
12.2 编码问题
iconv -f GBK -t UTF-8 input.json | jq '.'
jq -R '.' input.txt
jq '.' data.json
jq -r '.key' data.json
12.3 性能优化
jq -s '.[] | select(.type == "error")' huge.json
jq -cn 'inputs | select(.type == "error")' huge.json
jq -c '{id, name, timestamp}' huge.json
jq --stream 'select(length == 2) | .[1]' huge.json
head -1000 huge.json | jq '.'
性能建议:
- 小文件(< 10MB):无需考虑优化,直接使用标准模式
- 中等文件(10-100MB):使用
inputs 流式处理,避免 -s
- 大文件(> 100MB):使用
--stream 选项,或先截取样本分析
- 超大文件(> 1GB):考虑使用专用工具(如 jq 的 C 库绑定)或分批处理
12.4 调试技巧
jq 'try .field catch empty' data.json
jq '.' data.json
jq '.users' data.json
jq '.users[] | .name'
jq '.users[] | select(.age > 18) | .name'
jq '.users | debug | .[] | {name, age}' data.json
jq -e '.users[0]' data.json >/dev/null && echo "找到用户"
result=$(jq -e '.nonexistent' data.json 2>/dev/null) || echo "字段不存在"
学习 jq 的推荐路径
- 官方手册 -- jq Manual(最权威的参考)
- jq 教程 -- 官方交互式教程
- jq play -- jqplay.org(在线实验场)
- jq 备忘单 -- 各大技术社区的 jq Cheatsheet
- 实践出真知 -- 在每天的 API 调试、配置管理、日志分析中刻意使用 jq
十三、核心总结
jq 学习路线图
掌握 jq 是一个从基础到高阶的逐步积累过程,以下是建议的学习路径:
属性查询
→
数组操作
→
对象转换
→
管道组合
→
条件过滤
→
字符串处理
→
高级函数
→
实战应用
13.1 jq 命令速查表
| 分类 | 命令/过滤器 | 说明 |
| 基础 | jq '.' file | 格式化输出 JSON |
jq '.key' file | 获取属性值 |
jq '.a.b.c' file | 获取嵌套属性 |
jq '.key1, .key2' file | 获取多个属性 |
| 数组 | jq '.[]' file | 展开数组 |
jq '.[0]' file | 按索引访问 |
jq '.[1:3]' file | 数组切片 |
jq 'map(.key)' file | 映射变换 |
| 过滤 | jq 'select(.k > v)' file | 条件过滤 |
jq 'unique' file | 数组去重 |
jq 'group_by(.k)' file | 分组 |
jq 'sort_by(.k)' file | 排序 |
| 变换 | jq '{new: .old}' file | 字段重命名 |
jq 'del(.key)' file | 删除字段 |
jq '. + {k: v}' file | 添加字段 |
jq 'with_entries(f)' file | 键值对变换 |
| 输出 | jq -r '.key' file | 原始字符串输出 |
jq -c '.' file | 紧凑格式输出 |
jq -s '.' f1 f2 | 合并多个文件 |
jq '.[] | @csv' file | CSV 格式输出 |
13.2 三大黄金原则
原则一:理解流式处理
jq 的每个过滤器接收一个输入值,产生零个或多个输出值。管道将左侧的输出逐个传递给右侧。这种"数据流"模型是 jq 高效和灵活的根源。理解 .[](展开为多个值)与 map()(保持为一个数组)的区别至关重要。
原则二:从简单到复杂,逐步构建
永远不要试图一次写出复杂的 jq 过滤器。正确的做法是:先用 jq '.' 查看数据结构,然后逐步添加属性访问、数组操作、过滤条件、变换函数。每步验证结果,确保理解当前操作的含义。遇到复杂需求时,可以定义中间变量或拆分多个步骤。
原则三:管道组合而非一站式完成
jq 的精髓在于通过管道组合简单的过滤器来完成复杂的任务。不要试图在一个过滤器中完成所有事情。将任务分解为:数据提取 → 过滤筛选 → 数据变换 → 格式化输出 四个阶段。每个阶段使用最合适的过滤器和函数。
13.3 常用组合模式集锦
jq 'map(select(.active)) | sort_by(-.score) | .[:10] | [.[] | {name, score}]'
jq 'group_by(.category) | map({category: .[0].category, count: length, total: map(.amount) | add}) | sort_by(-.total)'
jq '[.users[] | .orders[] as $o | {user: .name, product: $o.product, price: $o.price}]'
jq 'walk(if type == "string" then gsub("password"; "***") elif type == "number" then . * 2 else . end)'
jq -s '{users: .[0].users, orders: .[1].orders}' users.json orders.json
13.4 推荐学习资源
最终寄语
jq 是现代开发者的瑞士军刀,它让 JSON 数据处理变得像 Shell 文本处理一样流畅自然。初学时可能会对 jq 的语法感到陌生,但一旦掌握了它的核心概念(管道、过滤器、迭代),你会发现处理 JSON 数据从未如此优雅高效。
记住:每一个数据结构都有最适合它的查询语言。对于 JSON,jq 就是那个答案。每天多用、多看 jq 的 --help 输出和官方手册,jq 会成为你数据处理工具箱中最锋利的刀刃。
10001