jq 与 yq 是命令行处理 JSON 与 YAML 的两把手术刀。前者能把 API 返回的深层嵌套 JSON 一行取出关键字段,后者能直接在 Kubernetes 清单和 docker-compose.yml 上做原地修改还保留注释。本文按「安装校验 → 语法入门 → 取值实战 → 结构重塑 → YAML 批改 → CI 断言」的顺序,给出可直接粘贴运行的命令,并附能力对照表与五个高频坑的处置办法。如果你还在用 Python 临时脚本解析接口响应,读完这篇可以省掉一大半胶水代码。
一、为什么它们值得占用你的肌肉记忆
日常排障里有三类高频动作:从接口响应里挖一个字段、从 kubectl 输出里筛一批异常对象、把某个配置项批量改掉。用 Python 写脚本要开文件、导包、处理编码;用 grep 加 sed 拼字符串则脆弱到一改缩进就崩。jq 和 yq 的价值在于它们把「结构化数据查询」变成了可以嵌进管道的一等公民,和 curl、kubectl、docker 天然咬合。
另一个常被忽略的收益是可审计性。一条 jq 表达式写在 CI 脚本里,任何人都能看懂它在断言什么;而一段临时 Python 脚本往往三个月后连作者都要重读一遍。想系统补齐命令行工具箱,可以配合这篇后端开发者必备的 10 个命令行效率工具一起看,本文则把其中最值钱的两把纵向讲透。
二、安装与版本确认(第一个坑就在这)
jq 从 1.7 开始才有稳定的 --args 与 ltrimstr 等常用能力,建议直接上 1.7 以上。yq 的坑更大:市面上有两个完全不同的 yq——Go 写的 mikefarah/yq(v4 语法,本文全部基于它)和 Python 写的 kislyuk/yq(其实是 jq 的 YAML 包装)。部分发行版的 apt 源里装到的是后者,语法完全不兼容,先确认版本再动手。
# macOS
brew install jq yq
# Debian / Ubuntu:apt 里的 yq 可能是 python-yq,行为不同,建议直接下二进制
sudo apt-get install -y jq
sudo wget -qO /usr/local/bin/yq \
https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
sudo chmod +x /usr/local/bin/yq
# 确认版本:jq 需 1.7+,yq 需 v4(v3 语法完全不兼容)
jq --version # jq-1.7.1
yq --version # yq (https://github.com/mikefarah/yq/) version v4.44.3
三、jq 核心语法:五分钟够用版
过滤器与管道
jq 的世界里一切都是过滤器:. 代表当前输入,.name 取字段,多个过滤器用 | 串起来,语义和 shell 管道一致。最需要形成条件反射的是 -r 参数——它去掉输出字符串的双引号。凡是取值结果要继续喂给别的命令或赋值给 shell 变量,必须加 -r,否则你会得到带引号的字符串,后续比较全部失效。
echo '{"name":"api-gateway","port":8080,"tags":["edge","http"]}' > svc.json
# 取单个字段
jq '.name' svc.json # "api-gateway"
jq -r '.name' svc.json # api-gateway(-r 去引号,管道里必用)
# 嵌套取值 + 默认值(字段不存在时兜底,避免输出 null)
jq -r '.meta.owner // "unknown"' svc.json
# 数组:索引、负索引、展开成多行
jq -r '.tags[0]' svc.json # edge
jq -r '.tags[-1]' svc.json # http
jq -r '.tags[]' svc.json # 逐行输出每个元素
# 管道组合与内置函数
jq -r '.tags | length' svc.json # 2
jq -r 'keys[]' svc.json # name / port / tags
select 条件筛选:从一堆对象里挑出问题那个
select(条件) 是排障阶段用得最多的函数。它接收一个流,只让满足条件的元素通过。配合 .[] 把数组展开成流,就能实现「遍历加过滤」。字符串插值语法 \(表达式) 可以在输出时拼接多个字段,比事后 awk 切列干净得多。
# 从容器列表里挑出已退出的容器名
docker inspect $(docker ps -aq) \
| jq -r '.[] | select(.State.Running == false) | .Name'
# 从 GitHub API 取最近 5 个 tag
curl -s https://api.github.com/repos/mikefarah/yq/tags \
| jq -r '.[:5][] | .name'
# 多条件组合:状态码异常或耗时超 1s 的请求(NDJSON 逐行日志)
jq -r 'select(.status >= 400 or .duration_ms > 1000)
| "\(.status)\t\(.duration_ms)\t\(.path)"' access.ndjson
# has 判断字段是否存在,比 != null 更严谨
jq -r '.[] | select(has("error")) | .request_id' events.json
这类日志分析的思路,和排查磁盘 IO 或慢查询时的「先筛异常样本再看分布」是一脉相承的,可以对照磁盘 I/O 打满导致服务雪崩排查实录里的定位路径理解。
四、jq 进阶:重塑结构与生成报表
jq 不只能取值,还能重新组装输出结构。{key: 表达式} 构造对象,[...] 构造数组,@tsv 和 @csv 把数组转成制表符或逗号分隔的一行文本——这是把接口数据倒进 Excel 或喂给 awk 最省事的通道。group_by 加 map 则能在命令行里直接做聚合统计,临时报表不用再开 Python。
# 重塑成扁平对象
jq '{svc: .name, addr: "0.0.0.0:\(.port)"}' svc.json
# 数组转 TSV:Pod 名 / 状态 / 容器数,直接贴进表格
kubectl get pods -o json \
| jq -r '.items[]
| [.metadata.name, .status.phase, (.spec.containers | length)]
| @tsv'
# group_by 聚合:按 phase 汇总数量
kubectl get pods -o json \
| jq -r '[.items[].status.phase] | group_by(.)
| map({phase: .[0], count: length})
| .[] | "\(.phase)\t\(.count)"'
# 多文件合并统计:-s 把多个输入吞成一个数组
jq -s 'map(.latency_ms) | {n: length, avg: (add / length), max: max}' bench-*.json
还有两个参数值得记住。-e 让 jq 按结果真假返回退出码,是写 CI 断言的关键;-n 表示不读输入、纯生成 JSON,用来手搓请求体特别顺手,比在 shell 里拼字符串安全得多。做接口调试时它和图形化工具是互补关系,选型可以参考API 调试工具选型:Postman 与 Bruno 实战。
五、yq 实战:原地批改 YAML 而不毁格式
yq v4 的语法几乎照搬 jq,学会一个另一个几乎零成本。它真正的杀手级能力是 -i 原地写回,并且在改动时保留原文件的注释、键顺序和缩进风格——这是 sed 和 Python 的 yaml 库都做不到的(后者往往把注释全部吃掉、键顺序重排)。对于人工维护的 Kubernetes 清单和 Helm values,这点差别决定了改动能不能进 Git 提交。
# 读:取镜像
yq '.spec.template.spec.containers[0].image' deploy.yaml
# 原地改镜像 tag(注释、键顺序、缩进全部保留)
yq -i '.spec.template.spec.containers[0].image = "registry.cn/app:v2.3.1"' deploy.yaml
# 加统一标签:键名含点号要用引号包住
yq -i '.metadata.labels."app.kubernetes.io/managed-by" = "platform"' deploy.yaml
# 多文档 YAML(--- 分隔):只改 kind 为 Deployment 的那一份
yq -i 'select(.kind == "Deployment") | .spec.replicas = 3' bundle.yaml
# 清理 kubectl 导出的运行时噪音,让文件能重新 apply
yq -i 'del(.metadata.uid, .metadata.resourceVersion,
.metadata.creationTimestamp, .status)' exported.yaml
# 环境变量注入:用 env() 把外部值安全带进表达式
TAG=v2.4.0 yq -i '.image.tag = env(TAG)' values.yaml
批量改多个文件时可以配合 find 和 xargs。若你的清单已经交给 Helm 管理,多数场景应该改 values 而不是渲染后的产物,具体分工见Helm Chart 实战:K8s 应用打包与版本管理。日常在集群里查对象状态,用 k9s 终端管理比反复敲 kubectl 更快,两者搭配是「看用 k9s、改用 yq」。
六、YAML 与 JSON 双向流水线
yq 的 -o=json 能把 YAML 转成 JSON,于是「用 yq 转格式、用 jq 做复杂查询」成了一条很实用的组合拳;反向用 -P 可以把 JSON 美化成 YAML。当查询逻辑涉及深层递归、复杂聚合时,jq 的函数库比 yq 更成熟,转过去处理更省心。
# YAML → JSON,交给 jq 做复杂查询
yq -o=json '.' values.yaml | jq -r '.ingress.hosts[].host'
# JSON → YAML(-P 输出规范缩进)
jq -n '{replicas: 3, image: "app:v1"}' | yq -P '.'
# CI 断言一:生产副本数不得低于 2
replicas=$(yq '.spec.replicas' deploy.yaml)
if [ "$replicas" -lt 2 ]; then
echo "生产 Deployment 副本数必须不小于 2,当前 $replicas"
exit 1
fi
# CI 断言二:健康检查契约,字段缺失或依赖异常直接失败
curl -s http://localhost:8080/healthz \
| jq -e '.status == "UP" and (.deps | all(.ok))' > /dev/null \
|| { echo "健康检查未通过"; exit 1; }
# CI 断言三:禁止镜像使用 latest 标签
yq -e '.spec.template.spec.containers[].image | test(":latest$") | not' deploy.yaml
把这三条断言放进流水线的 lint 阶段,能拦住相当比例的低级配置事故。流水线怎么组织可以参考GitHub Actions 实战,而把这些长命令收敛成 make lint 之类的短入口,见Makefile 与 just 让命令可复用。
七、能力对照:什么时候用哪个
| 维度 | jq 1.7+ | yq v4(mikefarah) |
|---|---|---|
| 输入格式 | JSON、NDJSON | YAML、JSON、XML、CSV、TOML、properties |
| 原地修改 | 不支持,需重定向到临时文件 | -i 原生支持 |
| 保留注释与键顺序 | 不适用 | 支持,改动最小化 |
| 多文档处理 | 需 -s 合并 | 原生识别 --- 分隔 |
| 函数库丰富度 | 强,聚合与递归函数完备 | 够用,复杂聚合建议转 JSON 交给 jq |
| 典型战场 | API 响应、日志分析、报表导出 | K8s 清单、Helm values、compose、CI 配置 |
一句话决策:数据是「读出来分析」的,用 jq;数据是「改回去提交」的,用 yq。两者交界处(YAML 里做复杂统计)就走 yq -o=json | jq。
八、五个高频坑与处置
| 现象 | 根因 | 处置 |
|---|---|---|
| 取出的值带双引号,shell 比较永远不相等 | 忘了 -r | 凡赋值给变量或进管道,一律 jq -r / yq 默认已去引号 |
| yq 报语法错误,表达式明明照文档写的 | 装到了 python-yq(kislyuk 版) | yq --version 确认是 mikefarah v4,否则换二进制安装 |
字段不存在时输出 null 污染下游 | 未做兜底 | 用 // "默认值" 或先 select(has("k")) 过滤 |
| 原地修改后文件被清空 | 用了 cmd file > file 重定向覆盖 | yq 用 -i;jq 必须先写临时文件再 mv |
| 含点号或斜杠的键取不到值 | 点号被当成层级分隔符 | 用引号包裹键名,如 ."app.kubernetes.io/name" |
第四条尤其致命。shell 的重定向在命令执行前就会截断目标文件,所以 jq '.' a.json > a.json 一定得到空文件。jq 至今没有官方 -i,标准做法是写到 a.json.tmp 再 mv 覆盖,或者用 sponge(moreutils 包)先把输入吞完再写。
九、落地清单
- 安装并确认
jq 1.7+与yq v4(mikefarah 版),写进团队开发机初始化脚本。 - 把最常用的三条表达式做成 shell 别名或 just 任务:查 Pod 状态表、查异常日志、改镜像 tag。
- CI 的 lint 阶段加上三条断言:副本数下限、禁用 latest 标签、健康检查契约。
- 所有 jq 取值场景默认加
-r;所有可能缺失的字段配//兜底。 - 禁止在脚本里对同一文件做重定向覆盖,改用
-i或临时文件加mv。
这两个工具的学习曲线在前半小时最陡,越过 select 与 @tsv 之后基本就是查手册的事。真正的收益是从此不再为了「取一个字段」去写一次性脚本——排障时少一层上下文切换,往往就是定位速度的差距。




