结构化数据处理:jq 与 yq 实战

系统讲解 jq 的过滤器、管道与数组操作,yq 对 YAML 的读取与修改,以及从 API 响应提取数据、生成与合并配置文件的完整实战套路。

1. 为什么脚本需要结构化数据处理

一句话总结: 现代工具链的输出大量是 JSON/YAML,用 grep/awk 按文本硬抠易碎易错,jq/yq 才是按「结构」取数的正确姿势。

docker inspect、curl API、kubectl get -o json 的输出都是 JSON;Kubernetes 配置、GitHub Actions、Ansible 用 YAML。

# 传统文本处理:一旦字段顺序/缩进变化就碎
docker inspect nginx | grep -o '"Image": "[^"]*"'

# jq 按结构取数:稳定可靠
docker inspect nginx | jq -r '.[0].Image'

# 检查工具是否就绪
command -v jq || sudo apt-get install -y jq
command -v yq || sudo snap install yq

1.1 JSON 与 YAML 的关系

YAML 1.2 是 JSON 的超集,二者可互相转换;yq 正是「YAML 界的 jq」。

# JSON 转 YAML
echo '{"name": "web", "port": 80}' | yq -P

# YAML 转 JSON
printf 'name: web\nport: 80\n' | yq -o json

# 验证 JSON 合法性与格式化
echo '{"a":1,"b":2}' | jq .

一句话总结: jq 处理 JSON,yq 处理 YAML,-P/-o json 让二者可以互转,形成统一的配置处理管道。

1.2 从哪个输入读

jq 既读文件也读 stdin,错误时退出码非零,便于接入脚本。

# 从文件读
jq '.users' /tmp/data.json

# 从管道读
curl -s https://api.github.com/repos/github/gitignore | jq '.stargazers_count'

# 从变量读
payload='{"status":"ok"}'
echo "$payload" | jq -r '.status'

2. jq 基础:过滤器与管道

一句话总结: jq 是「流式处理 JSON」的 DSL:. 代表当前值,.a.b 取字段,| 把上一步结果传给下一步。

# 取顶层字段
echo '{"name":"web","port":80}' | jq '.name'

# 嵌套字段
echo '{"server":{"host":"127.0.0.1"}}' | jq '.server.host'

# 管道:先取数组再取字段
echo '[{"id":1},{"id":2}]' | jq '.[] | .id'

2.1 数组遍历

.[] 把数组「炸开」成多个元素流,每个元素独立继续处理。

# 取所有元素的 id
echo '[{"id":1,"name":"a"},{"id":2,"name":"b"}]' | jq '.[].id'

# 取每个元素的两个字段
echo '[{"id":1,"name":"a"}]' | jq '.[] | {id, name}'

# 数组下标与切片
echo '[10,20,30,40]' | jq '.[1]'     # 20
echo '[10,20,30,40]' | jq '.[1:3]'   # [20,30]

一句话总结: . 返回数组整体、.[] 逐元素处理、.[i] 按下标取——三种形态对应「整体/遍历/索引」三种需求。

2.2 构造新对象

用 {} 字面量重组输出结构,是「裁剪 API 响应」的核心手法。

echo '[{"id":1,"name":"a","secret":"x"}]' | jq '.[] | {id, name}'
# {"id":1,"name":"a"}

# 重命名字段
echo '{"old":1}' | jq '{new: .old}'

# 增加常量字段
echo '{}' | jq '{env: "prod"}'

3. jq 进阶:select、map 与函数

一句话总结: select(条件) 过滤元素、map(f) 对每个元素施加变换,两者是处理数组的左右手。

# 过滤:只保留 port>80 的服务
echo '[{"name":"a","port":80},{"name":"b","port":443}]' \
  | jq '[.[] | select(.port > 80)]'

# map 变换每个元素
echo '[1,2,3]' | jq 'map(. * 2)'
# [2,4,6]

# 组合:过滤后再映射
echo '[{"n":1},{"n":2},{"n":3}]' | jq 'map(select(.n >= 2) | .n)'

3.1 常用内置函数

length、keys、has、contains、sort 是高频函数。

# 长度
echo '[1,2,3]' | jq 'length'

# 对象的所有键
echo '{"a":1,"b":2}' | jq 'keys'

# 排序与去重
echo '[3,1,2,1]' | jq 'unique'

# 判断字段是否存在
echo '{"a":1}' | jq 'has("b")'   # false

一句话总结: length/keys/sort/unique/has 覆盖了「统计、遍历键、排序、去重、判存在」五类常规需求。

3.2 字符串与数值处理

# 字符串拼接
echo '{"name":"web"}' | jq -r '"服务名: " + .name'

# 数值运算
echo '{"cpu":0.8,"mem":0.5}' | jq '.cpu * 100'

# 格式化数字
echo '{"ratio":0.123456}' | jq '(.ratio * 10000 | round) / 100'

# 模板插值
echo '{"user":"alice"}' | jq -r '"欢迎, \(.user)!"'

4. yq 处理 YAML

一句话总结: yq 的语法与 jq 对齐,能用 jq 的思维处理 YAML,-P 输出再格式化、-i 原地修改。

# 读取嵌套字段
printf 'server:\n  host: 127.0.0.1\n  port: 8080\n' | yq '.server.port'

# 修改并输出(-i 原地写文件)
printf 'port: 8080\n' > /tmp/cfg.yml
yq -i '.port = 9090' /tmp/cfg.yml
cat /tmp/cfg.yml

# 追加列表元素
yq -i '.servers += [{"name": "web2"}]' /tmp/cfg.yml

4.1 与 jq 的语法差异

yq 尽量对齐 jq,但 YAML 的锚点、多文档(---)是额外特性。

# 多文档 YAML:用 documentIndex 或迭代
printf -- '---\na: 1\n---\na: 2\n' | yq -N '.[].a'

# 锚点引用在转换时保留
printf 'base: &b\n  x: 1\ncopy: *b\n' | yq '.copy.x'

一句话总结: 90% 的 yq 用法与 jq 相同,差异集中在 YAML 特有的多文档与锚点语义上。

4.2 批量修改多个字段

# 一次修改多个字段
printf 'a: 1\nb: 2\n' | yq '.a = 10 | .b = 20'

# 删除字段
printf 'a: 1\nb: 2\n' | yq 'del(.a)'

# 深合并(merge)
yq -n '{"a": {"x": 1}} * {"a": {"y": 2}}'

5. 配置生成与模板组合

一句话总结: 用 jq/yq 生成配置比字符串拼接更安全:结构天然正确,特殊字符自动转义。

5.1 从变量构建 JSON

#!/usr/bin/env bash
set -euo pipefail

name="web-$BUILD_ID"
port="$PORT"
jq -n \
  --arg name "$name" \
  --argjson port "$port" \
  '{name: $name, port: $port, env: "prod"}'

--arg 传入字符串、--argjson 传入 JSON 值,天然规避注入与转义问题。

一句话总结: --arg/--argjson 是 jq 的「安全入参通道」,外部变量一律走这里而不是字符串拼接。

5.2 YAML 配置模板

#!/usr/bin/env bash
set -euo pipefail

cat > /tmp/values.yml <<'EOF'
image:
  repository: nginx
  tag: 1.25
replicas: 1
EOF

# 用 yq 覆盖模板字段
yq -i \
  --arg tag "1.27" \
  --argjson replicas 3 \
  '.image.tag = $tag | .replicas = $replicas' \
  /tmp/values.yml
cat /tmp/values.yml

5.3 合并多个配置文件

# 基础配置与覆盖配置深合并
yq -n 'load("/tmp/base.yml") * load("/tmp/overlay.yml")'

# 或逐层合并输出
cat base.yml overlay.yml | yq -N 'reduce .[] as $d ({}; . * $d)'

6. API 数据提取实战

一句话总结: curl 拉取 + jq 提取 + 循环消费,是脚本与 HTTP API 交互的黄金三段式。

6.1 分页与字段提取

#!/usr/bin/env bash
set -euo pipefail
url="https://api.github.com/orgs/github/repos"
# 提取全部仓库名与 star 数
curl -s "$url" \
  | jq -r '.[] | "\(.full_name) \(.stargazers_count)"'

6.2 基于结果做决策

#!/usr/bin/env bash
set -euo pipefail

status=$(curl -s -o /dev/null -w '%{http_code}' \
  http://localhost:8080/health)
echo "HTTP $status"
if [ "$status" != "200" ]; then
  echo "服务不健康,触发告警"
  exit 1
fi

一句话总结: 先用 jq 抽出「决策字段」,再交给 if/case 判断,比直接对 JSON 文本做 grep 可靠得多。

6.3 批量处理响应数组

#!/usr/bin/env bash
set -euo pipefail

# 从 API 取所有 ID,逐条处理
ids=$(curl -s "$API_URL/items" | jq -r '.[].id')
for id in $ids; do
  echo "处理 $id"
  curl -s -X DELETE "$API_URL/items/$id"
done

7. 实战:CI 配置与日志分析

一句话总结: 把 jq/yq 接进 CI 流程,可以完成版本提取、配置注入、日志结构化分析等自动化任务。

7.1 从 package.json 提取版本

#!/usr/bin/env bash
set -euo pipefail
version=$(jq -r '.version' package.json)
echo "当前版本: $version"
# 输出到 GitHub Actions
echo "version=$version" >> "$GITHUB_OUTPUT"

7.2 日志结构化统计

#!/usr/bin/env bash
set -euo pipefail

# 统计每个接口的 5xx 数量
tail -1000 /var/log/nginx/access.log \
  | awk '{print $7, $9}' \
  | jq -R -s '
      split("\n") |
      map(select(length > 0) | split(" ") | {path: .[0], code: .[1]}) |
      group_by(.code) |
      map({code: .[0].code, count: length})
    '

一句话总结: jq 的 -R 读原始行、-s 一次性吞入,配合 awk 预处理,能把非结构日志转成可聚合的结构数据。

7.3 生成部署清单

#!/usr/bin/env bash
set -euo pipefail

# 从清单生成部署参数
cat > /tmp/deploy.json <<'EOF'
{
  "app": "web",
  "tag": "v1.2.0",
  "replicas": 3,
  "env": {
    "LOG_LEVEL": "info"
  }
}
EOF

# 读取用于下一步
tag=$(jq -r '.tag' /tmp/deploy.json)
replicas=$(jq -r '.replicas' /tmp/deploy.json)
echo "发布 $tag,副本数 $replicas"

8. 总结

环节要点
选型jq 管 JSON、yq 管 YAML,二者可互转
基础.a.b 取字段、`
构造{} 重组输出、--arg 安全传参
进阶select 过滤、map 变换、length/keys/sort 函数
yq语法对齐 jq,-i 原地修改、del 删除、深合并
模板--arg/--argjson 生成结构正确的配置
API 实战curl 拉取 + jq 提取 + 循环消费三段式
CI 集成版本提取、日志统计、部署清单生成

结构化数据处理把脚本从「文本泥潭」里解放出来:不管上游输出多复杂的 JSON/YAML,jq/yq 都能按结构精确取数、改造、生成。这套能力在 Git 自动化、部署发布脚本里会反复用到。下一步进入 Git 自动化实践,把 hooks 与版本脚本串起来。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「shell」更多文章

  1. 任务编排与 Makefile 实战
  2. 文件监控与事件驱动流水线实战
  3. 结构化数据清洗与报表生成实战