jq 与 yq 实战:命令行玩转 JSON 与 YAML

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 开始才有稳定的 --argsltrimstr 等常用能力,建议直接上 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_bymap 则能在命令行里直接做聚合统计,临时报表不用再开 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

批量改多个文件时可以配合 findxargs。若你的清单已经交给 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、NDJSONYAML、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.tmpmv 覆盖,或者用 sponge(moreutils 包)先把输入吞完再写。

九、落地清单

  1. 安装并确认 jq 1.7+yq v4(mikefarah 版),写进团队开发机初始化脚本。
  2. 把最常用的三条表达式做成 shell 别名或 just 任务:查 Pod 状态表、查异常日志、改镜像 tag。
  3. CI 的 lint 阶段加上三条断言:副本数下限、禁用 latest 标签、健康检查契约。
  4. 所有 jq 取值场景默认加 -r;所有可能缺失的字段配 // 兜底。
  5. 禁止在脚本里对同一文件做重定向覆盖,改用 -i 或临时文件加 mv

这两个工具的学习曲线在前半小时最陡,越过 select@tsv 之后基本就是查手册的事。真正的收益是从此不再为了「取一个字段」去写一次性脚本——排障时少一层上下文切换,往往就是定位速度的差距。

上一篇 2026 世界模型 World Model:具身智能引擎
下一篇 GraphRAG 实战:用知识图谱增强 RAG 检索准确性