深色模式
jq 处理 JSON
摘要:云 CLI 和绝大多数 HTTP 接口都返回 JSON,用 grep/sed 处理 JSON 既脆弱又难读。本文讲清 jq 的四个核心用法:取字段、过滤、重组、格式化输出,并给出几个运维常用的一行式。
适用环境
bash
which jq || sudo apt install -y jq
jq --version
# 准备样例
echo '{"code":0,"data":[{"host":"web01","cpu":72.5,"mem":61},{"host":"web02","cpu":11,"mem":30}]}' > s.json1
2
3
4
2
3
4
操作步骤
一、基本取值
bash
# 格式化输出(带高亮)
jq . s.json
# 取顶层字段
jq '.code' s.json
# 取数组元素与字段
jq '.data[0]' s.json
jq '.data[0].host' s.json
jq '.data[].host' s.json # 所有 host,每行一个
# 压缩输出(去掉引号,方便 Shell 使用)
jq -r '.data[].host' s.json
jq -c '.data' s.json # 单行压缩1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
-r(raw output)是配合 Shell 变量使用时的必备选项,否则字符串会带双引号。
二、过滤与选择
bash
# 找出 CPU > 50 的主机
jq '.data[] | select(.cpu > 50)' s.json
# 只输出 host 与 cpu
jq -r '.data[] | select(.cpu > 50) | "\(.host)\t\(.cpu)"' s.json
# 多条件
jq '.data[] | select(.cpu > 50 and .mem > 60)' s.json
# 判断字段是否存在
jq '.data[] | select(has("mem"))' s.json1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
三、重组与计算
bash
# 构造新对象
jq '{name: .data[0].host, load: .data[0].cpu}' s.json
# 字段重命名与新增
jq '.data[] | {hostname: .host, cpu_pct: .cpu}' s.json
# 计算:求 CPU 平均值、最大值
jq '[.data[].cpu] | {avg: (add/length), max: max}' s.json
# 排序与取 Top N
jq '[.data[] | select(.cpu > 0)] | sort_by(-.cpu) | .[0:2]' s.json
# 分组统计
jq 'group_by(.cpu > 50) | map({over50: .[0].cpu > 50, count: length})' s.json1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
四、输出成表格 / CSV
bash
# 制表符分隔
jq -r '.data[] | [.host, .cpu, .mem] | @tsv' s.json
# CSV(自动处理引号转义)
jq -r '.data[] | [.host, .cpu, .mem] | @csv' s.json
# 加表头
jq -r '["host","cpu","mem"], (.data[] | [.host,.cpu,.mem]) | @tsv' s.json1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
五、配合 curl 处理接口返回
bash
# 调用接口并提取字段
curl -sS 'https://api.example.com/v1/health' | jq -r '.data.status'
# 判断接口是否正常,用于监控脚本
check_api() {
local url="$1" expect="$2"
local got
got="$(curl -sS --max-time 5 "$url" | jq -r '.data.status' 2>/dev/null)"
[[ "$got" == "$expect" ]] && echo "OK $url -> $got" || echo "FAIL $url -> ${got:-无返回}"
}
check_api 'https://api.example.com/v1/health' 'ok'1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
注意
接口返回可能不是合法 JSON(例如返回了 HTML 错误页或空字符串)。jq 会报错并返回非零退出码,脚本里要判断退出码或加 ?// empty 兜底,避免把解析失败当成「正常但空」。
六、配合云 CLI
bash
# 取所有运行中实例的 ID 与私网 IP,输出为 TSV
aws ec2 describe-instances --filters Name=instance-state-name,Values=running \
| jq -r '.Reservations[].Instances[] | [.InstanceId, .PrivateIpAddress] | @tsv'
# 找出安全组对 0.0.0.0/0 开放 22 端口的规则
aws ec2 describe-security-groups \
| jq -r '.SecurityGroups[] as $g | $g.IpPermissions[]
| select(.FromPort == 22 and (.IpRanges[].CidrIp // "") == "0.0.0.0/0")
| "\($g.GroupId) 对全网开放 22"'
# 统计各地域实例数量
aws ec2 describe-instances \
| jq -r '[.Reservations[].Instances[].Placement.AvailabilityZone] | group_by(.) | map({az: .[0], n: length}) | .[] | "\(.az)\t\(.n)"'1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
七、错误处理与默认值
bash
# 字段可能不存在时用 // 提供默认值
jq -r '.data[].mem // 0' s.json
# 可选操作符,避免对 null 取字段报错
jq -r '.data[]?.host' s.json
# 解析失败时给出可读错误而不是崩溃
if ! out=$(jq -r '.data' s.json 2>/tmp/jq.err); then
echo "JSON 解析失败: $(cat /tmp/jq.err)" >&2
exit 1
fi1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
八、实战:把接口返回转成巡检报表
bash
#!/usr/bin/env bash
set -euo pipefail
API="${1:?需要传入接口地址}"
curl -sS --max-time 10 "$API" \
| jq -r '["HOST","CPU","MEM","STATUS"],
(.data[] | [.host, .cpu, .mem,
(if .cpu > 80 or .mem > 90 then "ALERT" else "OK" end)])
| @tsv' \
| column -t -s $'\t'1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
验证
- [ ]
jq .能正确格式化输出,jq -r输出无引号 - [ ]
select过滤结果与人工核对一致 - [ ]
@tsv/@csv输出能被awk或表格工具正常消费 - [ ] 传入非法 JSON 时脚本能报错退出而非静默通过
bash
echo "not json" | jq . ; echo "退出码=$?"1
常见坑
- 忘记
-r:jq '.host'输出"web01"带引号,赋给变量后比较字符串总是不相等。 - 管道优先级:
.data[] | select(...)与select(.data[] | ...)含义完全不同,注意管道位置。 - 数字与字符串混用:
select(.cpu > 50)在 cpu 是字符串"72.5"时会报错,需先tonumber。 - 大文件内存:
jq -s会把整个输入读进内存,处理超大日志应逐行处理(jq -c配合 while read)。 - 路径含特殊字符:字段名含
-或空格时需用.["a-b"]形式访问。