jq —— JSON 命令行处理器
一句话定义
jq 是给 JSON 用的"sed/awk"——读 JSON,按你给的表达式提字段 / 过滤 / 变形,输出新的 JSON 或纯文本。几乎所有现代 API 输出都是 JSON,K8s(kubectl ... -o json)、curl 调用 REST API、journalctl -o json ... 没有 jq 寸步难行。
典型场景
# kubectl 输出二次加工
kubectl get pods -o json | jq '.items[].metadata.name'
# 提取某个值
curl -s api.github.com/users/torvalds | jq '.public_repos'
# 看结构
echo '{"a":{"b":[1,2,3]}}' | jq
# 过滤数组
kubectl get pods -o json | jq '.items[] | select(.status.phase != "Running")'
安装
apt-get install -y jq # Ubuntu / Debian
yum install -y jq # CentOS / RHEL
brew install jq # macOS
K8s 节点训练营装机脚本里直接装上。
第 1 步:理解 jq 表达式的基础语法
jq 表达式 = 过滤器(filter)。给 jq 一个 JSON 输入,过滤器算出一个或多个输出。
最简单的过滤器:
echo '{"name":"k8s","version":1.28}' | jq '.' # 整体(pretty print)
echo '{"name":"k8s","version":1.28}' | jq '.name' # 提字段:k8s
echo '{"name":"k8s","version":1.28}' | jq '.version' # 1.28
. 表示当前输入。.name 表示输入对象的 name 字段。
字段访问
echo '{"a":{"b":{"c":42}}}' | jq '.a.b.c' # 42
# 数组下标
echo '[10,20,30]' | jq '.[0]' # 10
echo '[10,20,30]' | jq '.[-1]' # 30(倒数)
echo '[10,20,30]' | jq '.[0:2]' # [10,20](切片)
# 字段 + 数组混合
echo '{"items":[{"name":"a"},{"name":"b"}]}' | jq '.items[0].name' # "a"
.[] 展开数组
echo '[10,20,30]' | jq '.[]'
# 10
# 20
# 30
注意:.[] 输出多个值(流式),不是一个数组。后续操作对每个值都跑一遍。
echo '[{"name":"a"},{"name":"b"}]' | jq '.[].name'
# "a"
# "b"
字段可能不存在:?
echo '[{"name":"a"},{"id":1}]' | jq '.[].name' # 报错:can't index 1
echo '[{"name":"a"},{"id":1}]' | jq '.[].name?' # ✅ ? 容错,缺失就不输出
? 让 jq 在字段访问错误时返回空而不是报错。
管道 |(jq 内部的)
jq 的管道和 shell 管道很像,但是 jq 内部的:
echo '{"a":[1,2,3]}' | jq '.a | .[0]' # 1
echo '{"a":[1,2,3]}' | jq '.a[0]' # 1(等价)
echo '[{"n":"a"},{"n":"b"}]' | jq '.[] | .n' # "a" "b"
echo '[{"n":"a"},{"n":"b"}]' | jq '.[].n' # 等价
| 把左边的输出喂给右边继续处理。
选择字段并组成新对象
echo '{"name":"k8s","ver":1.28,"size":42}' | jq '{name, ver}'
# {"name":"k8s","ver":1.28}
echo '{"a":1,"b":2}' | jq '{x: .a, y: .b}'
# {"x":1,"y":2}
K8s 实际场景:
kubectl get pods -o json | jq '.items[] | {name: .metadata.name, status: .status.phase}'
select(...) 按条件过滤
echo '[1,2,3,4,5]' | jq '.[] | select(. > 2)'
# 3 4 5
kubectl get pods -o json \
| jq '.items[] | select(.status.phase != "Running") | .metadata.name'
# 列出所有非 Running 的 pod 名
kubectl get pods -A -o json \
| jq '.items[] | select(.metadata.namespace == "kube-system") | .metadata.name'
# kube-system 命名空间下的 pod
select(condition) —— 满足条件的值通过,否则丢弃。
-r 输出纯字符串(生产用得最多)
echo '{"name":"k8s"}' | jq '.name'
# "k8s" ← 带引号
echo '{"name":"k8s"}' | jq -r '.name'
# k8s ← 不带引号
-r (raw) 去掉 JSON 字符串的引号。把 jq 输出再交给 shell / 别的工具时几乎一定要 -r:
# 拿所有 pod 名做下一步
PODS=$(kubectl get pods -o json | jq -r '.items[].metadata.name')
for p in $PODS; do kubectl logs "$p"; done
没 -r 的话 $p 会带着引号 "podname",传给 kubectl 就出错了。
常用内置函数
| 函数 | 用途 |
|---|---|
keys | 取对象的所有 key(数组) |
values | 取对象所有 value |
length | 长度(数组 / 字符串 / 对象 key 数) |
type | 类型("object", "array", "string", "number", "boolean", "null") |
has("key") | 是否含某 key |
contains(value) | 是否包含某值 |
map(expr) | 对数组每个元素跑 expr |
select(cond) | 过滤 |
tostring / tonumber | 类型转换 |
split("sep") / join("sep") | 字符串拆 / 拼 |
unique / sort / reverse | 数组操作 |
group_by(expr) | 按表达式分组(返回二维数组) |
min / max / add | 聚合 |
to_entries / from_entries | 对象 ↔ key-value 数组 |
echo '[3,1,4,1,5,9,2,6]' | jq 'sort' # [1,1,2,3,4,5,6,9]
echo '[3,1,4,1,5,9,2,6]' | jq 'sort | reverse' # 倒序
echo '[3,1,4,1,5,9,2,6]' | jq 'unique' # [1,2,3,4,5,6,9]
echo '[3,1,4,1,5,9,2,6]' | jq 'add' # 31(求和)
echo '[3,1,4,1,5,9,2,6]' | jq 'length' # 8
echo '{"a":1,"b":2}' | jq 'keys' # ["a","b"]
map() 对数组每个元素操作
echo '[1,2,3]' | jq 'map(. + 10)' # [11, 12, 13]
echo '[1,2,3]' | jq 'map(. * .)' # [1, 4, 9]
kubectl get pods -o json \
| jq '.items | map(.metadata.name)' # 名字数组
map(expr) ≈ [.[] | expr] —— 对每个元素跑 expr、收集成数组。
字符串格式化
echo '{"a":1,"b":2}' | jq '"\(.a)/\(.b)"' # "1/2"
echo '{"a":1,"b":2}' | jq -r '"\(.a)/\(.b)"' # 1/2
# K8s pod 名 + 状态拼接
kubectl get pods -o json \
| jq -r '.items[] | "\(.metadata.name)\t\(.status.phase)"'
\(expr) 是字符串插值(jq 里的语法,不是 shell)。
训练营常用 jq 套路
套路 1:所有 pod 名 + namespace(表格化)
kubectl get pods -A -o json \
| jq -r '.items[] | "\(.metadata.namespace)/\(.metadata.name)"' \
| column -t -s/
套路 2:找非 Running 的 pod
kubectl get pods -A -o json \
| jq -r '.items[] | select(.status.phase != "Running")
| "\(.metadata.namespace)/\(.metadata.name) \(.status.phase)"'
套路 3:节点 IP 清单
kubectl get nodes -o json \
| jq -r '.items[].status.addresses[] | select(.type=="InternalIP") | .address'
套路 4:容器镜像清单
kubectl get pods -A -o json \
| jq -r '.items[].spec.containers[].image' \
| sort -u
套路 5:按 namespace 统计 pod 数
kubectl get pods -A -o json \
| jq -r '.items[].metadata.namespace' \
| sort | uniq -c | sort -rn
或者纯 jq:
kubectl get pods -A -o json \
| jq -r '.items
| group_by(.metadata.namespace)
| map({ns: .[0].metadata.namespace, count: length})
| .[] | "\(.count) \(.ns)"' \
| sort -rn
后者纯 jq、不靠 sort/uniq。但前者更直觉、且更容易记住。
套路 6:journalctl JSON 解析
journalctl -u kubelet --since "10 min ago" -o json \
| jq -r 'select(.PRIORITY|tonumber < 4) | "\(._SYSTEMD_UNIT): \(.MESSAGE)"'
套路 7:curl API 处理
# GitHub user info
curl -s https://api.github.com/users/torvalds \
| jq '{name, repos: .public_repos, followers}'
# 拿 token
TOKEN=$(curl -s -X POST ... | jq -r '.token')
套路 8:组合修改 JSON
echo '{"name":"a","items":[1,2,3]}' \
| jq '. + {extra: "yes"}'
# {"name":"a","items":[1,2,3],"extra":"yes"}
echo '{"name":"a","items":[1,2,3]}' \
| jq '.items += [4,5]'
# {"name":"a","items":[1,2,3,4,5]}
echo '{"name":"a"}' \
| jq '.name = "b"'
# {"name":"b"}
# 删字段
echo '{"a":1,"b":2}' | jq 'del(.a)'
# {"b":2}
多文档输入 / 输出
多个 JSON 用 --slurp / -s 合并成数组
echo '{"a":1}{"a":2}{"a":3}' | jq -s '.'
# [{"a":1},{"a":2},{"a":3}]
echo '{"a":1}{"a":2}{"a":3}' | jq -s 'map(.a) | add'
# 6
API 流式输出 / NDJSON 经常需要 -s 收成数组再处理。
紧凑输出 -c
echo '{"a":1,"b":[2,3]}' | jq -c '.'
# {"a":1,"b":[2,3]} ← 单行,不格式化
echo '[1,2,3]' | jq -c '.[]'
# 1
# 2
# 3
-c 在脚本里把 jq 输出再喂下一个工具时常用。
--arg 传入 shell 变量
NAME="kubelet"
kubectl get pods -A -o json | jq --arg n "$NAME" '.items[] | select(.metadata.name | contains($n))'
--arg name value 把 value 当字符串绑定到 jq 变量 $name。永远用 --arg 而不是字符串拼接——避免引号和注入问题。
COUNT=5
jq --argjson n "$COUNT" '.items[:$n]' # 数字 / 布尔用 --argjson
常见踩坑
坑 1:忘了 -r,下游 shell 解释不对
NAME=$(kubectl ... | jq '.metadata.name') # NAME='"my-pod"' 含引号
kubectl logs $NAME # 报错:pod "\"my-pod\"" not found
修:
NAME=$(kubectl ... | jq -r '.metadata.name')
坑 2:.[] 输出多个值,后续操作意外
echo '[{"a":1},{"a":2}]' | jq '.[] | length'
# 1 1 ← 对每个对象求 length
注意:.[] | f 是"对每个元素跑 f",不是"对整个数组跑 f"。要后者:
echo '[{"a":1},{"a":2}]' | jq 'length' # 2
坑 3:字段名含特殊字符
echo '{"my-name":"k8s"}' | jq '.my-name' # ❌ 解析成 (.my) - (name)
echo '{"my-name":"k8s"}' | jq '.["my-name"]' # ✅
echo '{"my-name":"k8s"}' | jq '."my-name"' # ✅
含 -、含数字开头、含空格的字段名,用 [".."] 语法访问。
坑 4:null 传播
echo '{}' | jq '.a.b.c'
# null ← 不报错,直接 null
链式访问中间任何一步缺失,jq 默认返回 null(不报错)。但有些操作对 null 报错:
echo '{}' | jq '.a.b.c | length' # 报错:null has no length
echo '{}' | jq '.a.b.c // "default" | length' # 用 // 给默认值 → 7
// 是默认值操作符(null / false 时取右边)。
坑 5:数字精度
echo '{"id":12345678901234567890}' | jq '.id'
# 12345678901234567000 ← 大数被截断
jq 用 IEEE 754 双精度。超过 2^53 的整数会丢精度。要处理大整数(K8s 资源 ID、时间戳纳秒):
jq --raw-output '.id' # 当字符串处理
或者用 Python。
坑 6:jq 卡住
kubectl logs my-pod | jq '.' # 看着卡住
jq 在等输入结束才输出。如果 stdin 是流式(kubectl logs -f),jq 会缓冲不输出。解决:
kubectl logs -f my-pod | jq -c --unbuffered '.'
# 或 stdbuf -oL jq ...
坑 7:jq 语法太多想偷懒
如果你的 jq 表达式超过 3-4 行,考虑用 Python json.loads:
import json, sys
data = json.load(sys.stdin)
for p in data['items']:
print(p['metadata']['name'])
更易调、易读。jq 在单行 shell 管道里最强;复杂逻辑请别硬怼。
一些有用的 alias
# .bashrc
alias jp='jq .' # pretty print
alias jc='jq -c .' # compact
alias jr='jq -r .' # raw output
# K8s 专用
alias kjq='kubectl get -o json' # kubectl 输出 JSON