jq 命令实战指南:JSON 数据处理的瑞士军刀
如果你经常在命令行里跟 JSON 打交道——无论是看 API 返回结果、分析日志、还是处理配置文件——你一定遇到过这种痛苦:对着满屏的 JSON 用 grep、sed、awk 拼凑提取逻辑,写出来的命令又臭又长,换个格式就全崩了。这时候你需要一把趁手的刀,而这把刀就是 jq。
jq 是什么?官方定义很简洁:"jq is a lightweight and flexible command-line JSON processor." 翻译成人话就是:它是一个专门用来在命令行里处理 JSON 的瑞士军刀。可以过滤、映射、聚合、格式化、甚至做数学运算,而且语法极其灵活,一条命令就能完成你用 Python 写十几行脚本才能搞定的事。
本文从零开始,带你掌握 jq 的核心用法,从安装到生产实战,全程可复现。
一、安装与环境准备
jq 是一个单二进制文件,没有运行时依赖,安装非常简单:
macOS
brew install jq
Ubuntu / Debian
sudo apt update && sudo apt install jq -y
CentOS / RHEL
sudo yum install jq -y
Windows
winget install jqlang.jq
# 或者用 scoop
scoop install jq
验证安装
jq --version
# 输出类似:jq-1.7.1
装好之后,我们搞点测试数据。创建一个 data.json:
{
"name": "Alois Blog",
"url": "https://alois.bond",
"posts": [
{
"id": 1,
"title": "jq 入门",
"category": "tools",
"tags": ["json", "cli", "devtools"],
"views": 1024,
"published": true,
"created_at": "2026-06-15T10:30:00Z",
"author": { "name": "Alois", "role": "admin" }
},
{
"id": 2,
"title": "Docker Compose 实战",
"category": "devops",
"tags": ["docker", "container", "devops"],
"views": 3580,
"published": true,
"created_at": "2026-06-20T14:00:00Z",
"author": { "name": "Alois", "role": "admin" }
},
{
"id": 3,
"title": "草稿:待发布文章",
"category": "draft",
"tags": [],
"views": 0,
"published": false,
"created_at": "2026-06-28T08:00:00Z",
"author": { "name": "Alois", "role": "admin" }
}
],
"stats": {
"total_views": 4604,
"total_posts": 3,
"avg_views_per_post": 1534.67
}
}
接下来所有例子都基于这份数据,你可以边看边试。
二、基础操作:从"看一眼"到"取出来"
2.1 格式化输出(最常用的命令)
如果你只学一个 jq 命令,那就是这个:
cat data.json | jq '.'
或者直接传文件:
jq '.' data.json
点号 . 是 jq 的身份过滤器(identity filter),它不做任何过滤,但会触发格式化。输出的 JSON 带语法高亮和缩进,比直接 cat 好看一百倍。
如果只需要紧凑输出:
jq -c '.' data.json # compact,一行输出
如果只需要纯文本(不带引号):
jq -r '.name' data.json # raw output
# 输出:Alois Blog(不带引号)
2.2 提取字段
提取顶层字段:
jq '.name' data.json
# 输出:"Alois Blog"
jq '.stats.total_views' data.json
# 输出:4604
提取数组元素:
# 取第一个元素(jq 索引从 0 开始)
jq '.posts[0]' data.json
# 取最后一个
jq '.posts[-1]' data.json
# 取前两个
jq '.posts[0:2]' data.json
2.3 管道操作
jq 的管道 | 跟 shell 管道很像,但它是在 jq 内部传递数据的:
# 先取 posts 数组,再取第一个元素,再取 title
jq '.posts | .[0] | .title' data.json
# 输出:"jq 入门"
这跟 .posts[0].title 等价,但管道写法在处理复杂转换时威力更大。
三、过滤器语法:jq 的真正灵魂
3.1 数组遍历与映射
.[] 是 jq 里最常用的操作之一——它把数组"拆开",每个元素单独输出:
# 遍历 posts 数组,输出每个 title
jq '.posts[].title' data.json
# 输出:
# "jq 入门"
# "Docker Compose 实战"
# "草稿:待发布文章"
结合 -r 拿到纯文本,可以直接喂给其他命令:
jq -r '.posts[].title' data.json | while read title; do
echo "处理文章:$title"
done
3.2 条件过滤:select()
select() 是 jq 的 WHERE 子句,按条件筛选数组元素:
# 找出浏览量超过 2000 的文章
jq '.posts[] | select(.views > 2000)' data.json
# 找出已发布的文章标题
jq '.posts[] | select(.published == true) | .title' data.json
# 输出:
# "jq 入门"
# "Docker Compose 实战"
# 找出包含特定标签的文章
jq '.posts[] | select(.tags | contains(["docker"])) | .title' data.json
# 输出:"Docker Compose 实战"
常见陷阱:select() 如果没匹配到任何元素,不会报错,只会静默返回空。这在脚本里可能造成困惑:
# 这个会返回空,不会报错
jq '.posts[] | select(.views > 99999) | .title' data.json
3.3 字段映射:map()
map() 对数组的每个元素做变换,返回新数组:
# 提取所有文章的 title,组成新数组
jq '.posts | map(.title)' data.json
# 输出:["jq 入门", "Docker Compose 实战", "草稿:待发布文章"]
# 给每篇文章加一个字段
jq '.posts | map(. + {site: "alois.bond"})' data.json
3.4 字段选择:提取子集
# 只取 id 和 title
jq '.posts[] | {id, title}' data.json
# 嵌套对象的字段
jq '.posts[] | {id, title, author_name: .author.name}' data.json
3.5 字符串与数学函数
jq 内置了丰富的函数:
# 字符串操作
jq '.name | length' data.json # 长度:10
jq '.name | ascii_upcase' data.json # 大写
jq '.name | startswith("Alois")' data.json # true
# 数学运算
jq '.stats.total_views / .stats.total_posts' data.json
# 输出:1534.666...
jq '.stats.avg_views_per_post | floor' data.json
# 输出:1534
四、聚合与分组:把 jq 当 SQL 用
这是 jq 真正超越 grep+sed 的地方。面对 JSON 数组,你可以直接做聚合分析。
4.1 求和、平均、最大最小
# 总浏览量
jq '[.posts[].views] | add' data.json
# 输出:4604
# 平均浏览量
jq '[.posts[].views] | add / length' data.json
# 最大浏览量
jq '[.posts[].views] | max' data.json
# 输出:3580
4.2 按字段分组:group_by()
# 按 category 分组
jq '.posts | group_by(.category)' data.json
配合 map() 可以做出 SQL 风格的聚合结果:
# 每个分类的文章数和总浏览量
jq '.posts | group_by(.category) | map({
category: .[0].category,
count: length,
total_views: (map(.views) | add)
})' data.json
输出:
[
{"category": "devops", "count": 1, "total_views": 3580},
{"category": "draft", "count": 1, "total_views": 0},
{"category": "tools", "count": 1, "total_views": 1024}
]
这已经很像一条 SELECT category, COUNT(*), SUM(views) GROUP BY category 了。
4.3 排序:sort_by()
# 按浏览量升序
jq '.posts | sort_by(.views)' data.json
# 按浏览量降序
jq '.posts | sort_by(.views) | reverse' data.json
4.4 唯一值与去重
# 所有标签(展开后去重)
jq '[.posts[].tags[]] | unique' data.json
# 输出:["cli", "container", "devops", "devtools", "docker", "json"]
五、实战场景:5 个你一定会遇到的用法
场景 1:格式化 curl 返回的 API 数据
# 请求 GitHub API 并提取关键信息
curl -s https://api.github.com/repos/jqlang/jq | jq '{
name,
stars: .stargazers_count,
forks: .forks_count,
language,
description
}'
# 只看最近 5 个 issue
curl -s https://api.github.com/repos/jqlang/jq/issues | jq '.[:5] | .[] | {
number, title, user: .user.login, state
}'
场景 2:解析 Docker 输出
# 列出所有运行中容器的名称和端口
docker ps --format '{{json .}}' | jq -s 'map({
name: .Names,
image: .Image,
ports: .Ports,
status: .Status
})'
# 检查哪个容器占用了 80 端口
docker inspect $(docker ps -q) | jq '.[] | select(
.NetworkSettings.Ports | to_entries[]?.value != null
) | {name: .Name[1:], ports: .NetworkSettings.Ports | keys}'
场景 3:处理 Nginx / 应用日志
假设你的应用输出 JSON 格式日志,每行一条记录(JSONL 格式):
# 统计每种状态码的数量
cat app.log | jq -r '.status' | sort | uniq -c | sort -rn
# 纯 jq 版本:统计 5xx 错误的请求路径
jq -r 'select(.status >= 500) | "\(.method) \(.path) [\(.status)]"' app.log
# 找出响应时间超过 1 秒的请求(假设字段是 response_time_ms)
jq 'select(.response_time_ms > 1000) | {path, time: .response_time_ms}' app.log
场景 4:处理 Kubernetes 输出
# 列出所有 Pod 的名称和状态
kubectl get pods -o json | jq '.items[] | {
name: .metadata.name,
status: .status.phase,
node: .spec.nodeName,
restarts: .status.containerStatuses[0].restartCount
}'
# 找出所有非 Running 状态的 Pod
kubectl get pods -o json | jq '.items[] | select(.status.phase != "Running") | .metadata.name'
场景 5:配置文件处理
# 读取 package.json 并提取依赖
jq '.dependencies | keys' package.json
# 检查某个依赖是否存在
jq '.devDependencies | has("typescript")' package.json
# 合并两个 JSON 配置文件
jq -s '.[0] * .[1]' config.json override.json
六、高级技巧:让 jq 飞起来
6.1 变量绑定:as
用 as 在管道中绑定变量,避免重复计算:
# 不用 as(重复取 length)
jq '.posts | (map(.views) | add) / length'
# 用 as 绑定(更清晰)
jq '.posts as $p | ($p | map(.views) | add) / ($p | length)' data.json
6.2 自定义函数
# 定义一个计算百分比的函数
jq 'def pct(a; b): (a / b * 100 | floor); .posts[] | {
title,
views,
pct: pct(.views; 4604)
}' data.json
6.3 条件判断:if-then-else
jq '.posts[] | {
title,
views,
level: (if .views > 2000 then "hot"
elif .views > 500 then "normal"
else "cold" end)
}' data.json
6.4 处理 null 值:alternative operator
# // 是 alternative operator,左边为 null/false 时用右边的值
jq '.posts[] | {title, tags: (.tags // ["untagged"])}' data.json
6.5 与外部变量交互:--arg
# 从 shell 变量传入 jq
TARGET_CATEGORY="devops"
jq --arg cat "$TARGET_CATEGORY" '.posts[] | select(.category == $cat) | .title' data.json
# 输出:"Docker Compose 实战"
# --argjson 用于传入 JSON 值(数字、布尔等)
MIN_VIEWS=2000
jq --argjson min "$MIN_VIEWS" '.posts[] | select(.views > $min) | .title' data.json
常见陷阱:--arg 传入的值始终是字符串,比较数字时请用 --argjson。
七、常用组合速查表
| 需求 | jq 命令 |
|---|---|
| 美化 JSON | jq '.' file.json |
| 紧凑输出 | jq -c '.' file.json |
| 取字段(纯文本) | jq -r '.field' file.json |
| 遍历数组 | jq '.[] | .field' file.json |
| 条件过滤 | jq '.[] | select(.field > 100)' |
| 提取子集 | jq '.[] | {a, b}' file.json |
| 数组映射 | jq 'map(.field)' file.json |
| 数组求和 | jq '[.[] | .field] | add' file.json |
| 按字段分组 | jq 'group_by(.field)' file.json |
| 按字段排序 | jq 'sort_by(.field)' file.json |
| 数组长度 | jq '.array | length' file.json |
| 去重 | jq 'unique' file.json |
| 合并多个文件 | jq -s '.[0] * .[1]' a.json b.json |
| 传入外部变量 | jq --arg key "$val" '.[$key]' |
| 只输出 key 名 | jq 'keys' file.json |
八、性能注意事项
jq 本身是用 C 写的,速度很快,但面对超大文件时仍有几个需要注意的点:
- 用 streaming 模式处理大文件:
jq --stream会把 JSON 拆成路径-值的流,内存占用极小。适合处理几 GB 的 JSON 文件。 - 避免多次遍历:如果要对同一个数组做多个聚合,用
as绑定到变量,只遍历一次:# 不好:两次遍历 jq '[.data[] | .price] | add' huge.json jq '[.data[] | .price] | length' huge.json # 好:一次遍历 jq '.data as $d | { total: ($d | map(.price) | add), count: ($d | length) }' huge.json - 管道中尽早过滤:把
select()放在管道前面,减少后续操作处理的数据量。 - 用 -c 减少输出开销:紧凑模式不产生缩进和换行,在管道传递大量数据时更快。
九、写在最后
jq 是我日常使用频率最高的命令行工具之一。它不是银弹——遇到极其复杂的嵌套转换,你可能还是需要写 Python 脚本——但它覆盖了命令行下 90% 的 JSON 处理需求,而且语法简洁、性能出色。
几个学习建议:
- 从
jq '.'开始,格式化输出本身就是最有用的功能。 - 把
jqplay.org加入书签——这是一个在线的 jq 沙盒,可以实时预览过滤器效果,调试语法超级方便。 - 熟记 5 个核心操作:
.(身份)、.[](展开数组)、select()(过滤)、map()(映射)、|(管道)。掌握了这 5 个,剩下的都是组合。 - 读官方手册:
man jq非常详尽,遇到不确定的用法直接查。
下次当你下意识地打开 Python 写一个 json.loads() + for 循环的脚本来处理 API 返回数据时,不妨先试试一行 jq。你可能会惊讶于它能省下多少时间。