Skip to content
返回博客
教程

jq 速查手册:30 个真实 JSON 命令行实战模式

用 30 个实战模式玩转 jq:从 kubectl、AWS CLI 到日志文件,命令行过滤、转换、提取 JSON 的所有套路一次讲清。

12 分钟阅读

jq 速查手册:30 个真实 JSON 命令行实战模式

你把 kubectl get pods -o json 丢进 less,终端在两兆字节的 JSON 上瞬间卡死。你只想要所有 Running 状态的 pod 名字。jq 用三个字符的 filter 语法就能做到——前提是你认识这套词汇。

这不是又一份语法参考。它是你真正会敲进终端的 30 个模式,按任务分组:访问、过滤、转换、聚合、格式化,以及与 kubectlawsdocker 这些真实工具组合使用。

何时用 jq,何时用浏览器 formatter,何时写代码

jq 并不总是正确答案。三个工具各有用武之地:

场景首选工具原因
单个 API 响应,要语法高亮和错误行号浏览器 JSON Formatter可视化、零配置、浏览器内私密
Shell pipeline、日志处理、CI 脚本、远程服务器jq可组合、可脚本化、不依赖 GUI
业务逻辑、单元测试、复杂分支写代码(JS / Python)真实调试器、类型、生态

当任务生活在 shell pipeline 里,选 jq;其他场景通常有更合适的工具。

安装与第一条 pipeline

jq 是每个主流平台上的单一二进制:

# macOS
brew install jq

# Debian / Ubuntu
sudo apt install jq

# Windows(winget)
winget install jqlang.jq

第一条 pipeline,使用 identity filter:

curl -s https://api.github.com/users/octocat | jq .

. filter 原样输出并美化格式。仅此一招就能替代大多数”让我打开 JSON 编辑器”的瞬间。

五个常用 flag 覆盖了 90% 的真实场景:

Flag用途
-r原始输出——去掉字符串结果外层的引号
-c紧凑输出——每行一个 JSON 值(NDJSON)
-sslurp——把所有输入合并成一个数组
-R原始输入——按行读取非 JSON 文本
-n空输入——不读 stdin,用 null 作为输入

核心心智模型:Filter 与 Pipe

Filter 接收一个 JSON 值作为输入,产出 0 个或多个 JSON 值。Filter 之间用 pipe | 组合,把左侧 filter 的每个输出送给右侧 filter。这和 shell pipe 是同一个模型,只是流动的不是字节而是 JSON 值。

# . —— identity
echo '{"name":"Alice"}' | jq '.'

# .key —— 字段访问
echo '{"name":"Alice"}' | jq '.name'

# .key.sub —— 深路径
echo '{"user":{"email":"a@x.com"}}' | jq '.user.email'

# .[] —— 遍历数组元素(产出多个输出)
echo '[{"id":1},{"id":2}]' | jq '.[] | .id'

# Pipe 组合:.items[] 的每个输出都喂给 .name
echo '{"items":[{"name":"a"},{"name":"b"}]}' | jq '.items[] | .name'

整套语法就这些。下面的 30 个模式全是这些原语的组合。

你真正会用的 30 个模式

每个模式给出输入 JSON、命令和输出。任何一个都能直接复制到终端。

访问与提取(模式 1–5)

模式 1 —— 用 ? 安全访问

访问一个可能不存在的字段,且不崩溃:

echo '{"name":"Alice"}' | jq '.address?.city?'
# 输出: null

? 抑制缺失键的错误。不加的话,如果 .address 不存在,.address.city 会抛出类型错误。

模式 2 —— 深路径访问

echo '{"user":{"profile":{"email":"a@x.com"}}}' | jq '.user.profile.email'
# 输出: "a@x.com"

模式 3 —— 数组切片

echo '[10,20,30,40,50]' | jq '.[1:3]'
# 输出: [20, 30]

echo '[10,20,30,40,50]' | jq '.[-1]'
# 输出: 50

负索引从尾部开始计数。切片遵循半开区间,和 Python 一样。

模式 4 —— 递归下钻抽取所有匹配键

echo '{"a":{"name":"x"},"b":[{"name":"y"},{"id":1}]}' | jq '.. | .name? | select(. != null)'
# 输出: "x"
#       "y"

.. 遍历整棵树的每个值。配合 .name?select,它抽取任意深度的所有 name 字段——探索未知 JSON schema 时的利器。

模式 5 —— 列出对象的所有键

echo '{"zebra":1,"apple":2,"mango":3}' | jq 'keys'
# 输出: ["apple", "mango", "zebra"]

echo '{"zebra":1,"apple":2,"mango":3}' | jq 'keys_unsorted'
# 输出: ["zebra", "apple", "mango"]

keys 按字母排序;keys_unsorted 保留插入顺序。

过滤(模式 6–10)

模式 6 —— 按条件过滤数组

echo '[{"age":20},{"age":30},{"age":40}]' | jq 'map(select(.age > 25))'
# 输出: [{"age":30},{"age":40}]

map(f) 对每个元素应用 fselect(cond) 只保留条件为真的元素。

模式 7 —— 字符串前缀匹配

echo '[{"name":"api-gateway"},{"name":"web-ui"},{"name":"api-auth"}]' \
  | jq '.[] | select(.name | startswith("api"))'
# 输出: {"name":"api-gateway"}
#       {"name":"api-auth"}

还有 endswith("...")contains("...")test("^regex$") 可用。

模式 8 —— 多条件组合

echo '[{"type":"A","count":5},{"type":"A","count":15},{"type":"B","count":20}]' \
  | jq '.[] | select(.type == "A" and .count > 10)'
# 输出: {"type":"A","count":15}

andornot 行为符合直觉。

模式 9 —— 删除敏感字段

echo '{"user":"alice","password":"s3cret","token":"abc"}' | jq 'del(.password, .token)'
# 输出: {"user":"alice"}

del() 可以接受多个路径,且缺失的路径不会报错。

模式 10 —— 按字段去重

echo '[{"id":1,"v":"a"},{"id":2,"v":"b"},{"id":1,"v":"a2"}]' | jq 'unique_by(.id)'
# 输出: [{"id":1,"v":"a"},{"id":2,"v":"b"}]

unique 对整个值去重;unique_by(f) 按 filter 结果去重。

转换(模式 11–15)

模式 11 —— 重命名字段

echo '[{"first_name":"Alice","age":30}]' | jq 'map({name: .first_name, age})'
# 输出: [{"name":"Alice","age":30}]

{age}{age: .age} 的简写。

模式 12 —— 用字符串插值添加计算字段

echo '[{"first":"Alice","last":"Chen"}]' \
  | jq 'map(. + {fullName: "\(.first) \(.last)"})'
# 输出: [{"first":"Alice","last":"Chen","fullName":"Alice Chen"}]

\(expr) 求值 expr 并把值插入字符串。

模式 13 —— 嵌套数组扁平化

echo '[{"tags":["a","b"]},{"tags":["c"]}]' | jq '[.[] | .tags[]]'
# 输出: ["a","b","c"]

echo '[[1,2],[3,[4,5]]]' | jq 'flatten'
# 输出: [1,2,3,4,5]

flatten 可接深度参数:flatten(1) 只剥一层。

模式 14 —— 对象与键值数组互转

echo '{"a":1,"b":2}' | jq 'to_entries'
# 输出: [{"key":"a","value":1},{"key":"b","value":2}]

echo '[{"key":"a","value":1},{"key":"b","value":2}]' | jq 'from_entries'
# 输出: {"a":1,"b":2}

这对组合让你能对对象的键做遍历处理——点路径语法本身做不到。

模式 15 —— 对象深度合并

echo '{"a":{"x":1},"b":2}' | jq '. * {a:{y:9}, c:3}'
# 输出: {"a":{"x":1,"y":9},"b":2,"c":3}

* 做深度合并。浅合并用 +(右侧覆盖左侧)。

聚合与统计(模式 16–20)

模式 16 —— 数组、对象、字符串的长度

echo '[1,2,3,4]' | jq 'length'      # 4
echo '{"a":1,"b":2}' | jq 'length'  # 2
echo '"hello"' | jq 'length'        # 5

模式 17 —— 对字段求和

echo '[{"price":10},{"price":25},{"price":5}]' | jq '[.[].price] | add'
# 输出: 40

add 对数字求和、对字符串拼接、对数组合并——根据输入类型决定。

模式 18 —— 按字段分组

echo '[{"cat":"A","n":1},{"cat":"B","n":2},{"cat":"A","n":3}]' | jq 'group_by(.cat)'
# 输出: [[{"cat":"A","n":1},{"cat":"A","n":3}],[{"cat":"B","n":2}]]

每个分组变成一个内部数组。配合 map 可以对每组再聚合。

模式 19 —— 降序排序

echo '[{"date":"2026-01-03"},{"date":"2026-01-01"},{"date":"2026-01-02"}]' \
  | jq 'sort_by(.date) | reverse'
# 输出: [{"date":"2026-01-03"},{"date":"2026-01-02"},{"date":"2026-01-01"}]

ISO 8601 日期字符串作为字符串排序就正确。其他格式需要先解析——Unix 时间戳完全指南覆盖了 epoch 秒、毫秒和时区转换的深入细节。

模式 20 —— 按字段取最值

echo '[{"name":"a","rating":4.1},{"name":"b","rating":4.8},{"name":"c","rating":3.9}]' \
  | jq 'max_by(.rating)'
# 输出: {"name":"b","rating":4.8}

min_bymax_by 返回单个元素。取前 N 个用 sort_by(.rating) | reverse | .[:N]

格式化输出(模式 21–25)

模式 21 —— CSV 输出

echo '[{"name":"Alice","age":30},{"name":"Bob","age":25}]' \
  | jq -r '.[] | [.name, .age] | @csv'
# 输出: "Alice",30
#       "Bob",25

@csv 自动加引号并转义字符串内的引号。-r 去掉外层 JSON 字符串的引号,CSV 才能直接 pipe。管道中 CSV 与 JSON 的完整互转流程见 CSV 与 JSON 互转指南

模式 22 —— TSV 输出

echo '[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]' \
  | jq -r '.[] | [.id, .name] | @tsv'
# 输出: 1	Alice
#       2	Bob

Tab 分隔的输出与 cutawkcolumn -t 配合顺畅。

模式 23 —— 原始字符串输出

echo '["alpha","beta"]' | jq -r '.[]'
# 输出: alpha
#       beta

没有 -r,每行都会带引号。原始输出才是你喂给 xargswhile read、其他 shell 命令的格式。

模式 24 —— NDJSON / JSON Lines

echo '[{"a":1},{"a":2}]' | jq -c '.[]'
# 输出: {"a":1}
#       {"a":2}

每行都是独立 JSON 值——Kafka、Elasticsearch、大多数结构化日志用的格式。-c 还会去掉所有内部空白。

模式 25 —— 字符串插值格式化输出

echo '[{"name":"server-1","cpu":0.73},{"name":"server-2","cpu":0.21}]' \
  | jq -r '.[] | "\(.name): \(.cpu * 100)% CPU"'
# 输出: server-1: 73% CPU
#       server-2: 21% CPU

做摘要和日志行时,比原始 JSON 干净得多。

DevOps 实战(模式 26–30)

模式 26 —— kubectl:列出所有 Running pod 名字

kubectl get pods -o json \
  | jq -r '.items[] | select(.status.phase=="Running") | .metadata.name'

Pipeline 思路:遍历 pods,只保留 Running,把名字输出成原始字符串。

模式 27 —— AWS EC2:实例 ID 和公网 IP

aws ec2 describe-instances \
  | jq -r '.Reservations[].Instances[] | [.InstanceId, .PublicIpAddress // "none"] | @tsv'

// 是 alternative operator,在字段为 null 时提供默认值——避免输出列里出现字面 null

模式 28 —— GitHub API:合并分页结果

for p in 1 2 3; do
  curl -s "https://api.github.com/orgs/myorg/repos?per_page=100&page=$p"
done | jq -s 'add | map(.name)'

-s 把所有响应 slurp 成数组的数组,add 拼接,再 map(.name) 抽名字。任何分页 API 都能套用。

模式 29 —— 筛选结构化日志

cat app.log | jq -c 'select(.level=="error")'

前提是日志是 NDJSON(每行一个 JSON 对象)。配合 tail -f 做实时监控:

tail -f app.log | jq -c 'select(.level=="error") | {ts: .timestamp, msg: .message}'

模式 30 —— Docker:当前使用的所有镜像

docker inspect $(docker ps -q) | jq -r '.[].Config.Image' | sort -u

快速查看一台主机上跑了哪些镜像版本,方便不过。

常见错误与修法

每个 jq 用户都遇过这几种报错。知道修法能省下不少时间。

Cannot iterate over null (null)

你试图遍历的字段是 null 或不存在。两种修法:

# 方案 A:optional operator
echo '{}' | jq '.items[]?'
# 输出: (空,不报错)

# 方案 B:alternative operator 提供默认值
echo '{}' | jq '(.items // [])[]'
# 输出: (空,不报错)

想静默跳过用 ?;想强制给一个具体的空数组让后续 filter 继续跑,用 // []

Cannot index array with "key"

你写了 .foo 但当前值是数组。加 [] 遍历:

# 错
echo '{"users":[{"name":"Alice"}]}' | jq '.users.name'
# Error: Cannot index array with "name"

# 对
echo '{"users":[{"name":"Alice"}]}' | jq '.users[].name'
# 输出: "Alice"

Shell 引号坑

整个 jq 程序用单引号包裹,程序内部用双引号做字符串字面量:

# 可移植
jq '.users[] | select(.role == "admin")'

# 会炸 —— 外层双引号被 shell 先解释
jq ".users[] | select(.role == \"admin\")"

Windows PowerShell 边缘情况

PowerShell 不把单引号当一回事。要么外层用双引号并转义内部引号,要么用 here-string:

jq "@'
.users[] | select(.role == \"admin\")
'@"

复杂 filter 直接存成 .jq 文件,用 jq -f filter.jq 执行。

-r 的误用

-r 只对字符串结果生效。对对象用 -r 照样输出 JSON 对象:

echo '{"a":1}' | jq -r '.'
# 输出: {"a":1}     ← 原样;-r 没东西可脱引号

想去掉某个字段的引号,先 select 那个字段:jq -r '.a'

jq 拒绝带注释或尾逗号的 JSON

echo '{"a": 1, /* 备注 */ "b": 2,}' | jq .
# parse error: Invalid numeric literal

jq 严格遵循 RFC 8259 JSON——不支持注释、尾逗号、未加引号的键。如果文件是 JSON5 或 JSONC(配置文件常见),先剥离这些扩展再喂给 jqJSON5 与 JSONC 格式化指南讲解了哪些解析器支持它们,以及如何转换为严格 JSON。

jq 的替代品:gron、fx、jj、yq

jq 不是唯一选择,有时别的工具更顺手:

工具强项何时选用
gron把 JSON 展平成可 grep 的路径字符串探索未知 schema,你不知道 key 在哪
fx交互式 TUI explorer,带高亮手动浏览大 JSON 文件
jjjq 快很多,语法受限处理百万级记录的热循环
yq语法和 jq 一致但处理 YAMLKubernetes manifest、CI 配置
浏览器 JSON Formatter语法高亮、精确错误信息、零安装开发期调试单个响应

日常 shell 活儿选 jq,组合性最好。一次性探索用 gron 更快。YAML 就用 yq,别试图 yq-then-jq 组合。

日常 Pro Tips

几个让 jq 像手套一样顺手的小习惯:

  1. 写一个 .jqrc 放在 $HOME。把常用 function 扔进去,每次 jq 调用都能直接使用:

    def running: select(.status.phase == "Running");
    def table(f): [f] | @tsv;
  2. 复杂 filter 在 jqplay.org 上调。左边贴 JSON,右边改 filter,调好了再挪进脚本。

  3. history 建自己的 cheat sheethistory | grep 'jq ' | sort -u > ~/jq-patterns.txt 把你真实用过的模式全抓下来。

  4. 遇到陌生 schema,先用浏览器 JSON Formatter 可视化探索结构,找到路径再写 jq 命令。

  5. 监控实时值watch -n 5 "curl -s api.example.com/health | jq '.uptime'" 每 5 秒刷新一次——零依赖的小 ops dashboard。

FAQ

什么是 jq,开发者为什么用它?

jq 是命令行 JSON 处理器。它让你在 shell pipeline 里抽取、过滤、转换 JSON,而不用写 Python 或 Node 脚本——从 API 响应、日志文件、kubectl 输出到目标字段的最短路径。

jq 在 Windows 上能用吗?

能。用 winget install jqlang.jq、Chocolatey 的 choco install jq,或者从 jqlang.org 下载二进制。PowerShell 的引号规则和 bash 不一样——拿不准时就把 filter 存成 .jq 文件,用 jq -f filter.jq 执行。

jq 和浏览器 JSON formatter 有什么区别?

浏览器 JSON Formatter 是交互式——贴 JSON、看高亮和错误、复制结果。jq 是非交互式——用 filter 一次性描述转换,在 shell pipeline 里跑。调试单个响应用浏览器;对成百上千个输入自动化同一操作用 jq

为什么 jq 报 “Cannot iterate over null”?

你试图遍历(.[])一个 null 值——通常因为输入里该字段缺失。用 optional operator .items[]?,或提供默认值 .items // [] | .[] 修复。

jq 能原地修改文件吗?

不能直接修改——jq 写到 stdout。用临时文件,或 moreutils 里的 spongejq '.version = "2.0"' config.json | sponge config.json。操作前先备份原文件;filter 写错会把文件覆盖。

怎么配合 curl 响应使用 jq?

curl -s pipe 进 jq-s 关掉 curl 的进度条,只让 JSON 响应体流进 jq

curl -s https://api.github.com/users/octocat | jq '.name, .blog'

jq 的 | 和 shell 的 | 有什么区别?

Shell pipe 在进程之间传字节。jq 的 pipe 在同一 jq 进程里的 filter 之间传 JSON 值。单条 jq 命令带多个内部 pipe 只跑一个进程——比 jq | jq | jq 串联便宜。

jq 能处理 JSON Lines(NDJSON)吗?

原生支持。当输入由空白分隔时,jq 把每行当独立 JSON 值读。用 -c 输出 NDJSON,-s 把 NDJSON 收成单个数组。

怎么光美化 JSON,不做任何 filter?

用 identity filter:cat data.json | jq .jq . < data.json。它会解析、校验、用两空格缩进美化——不需要写 filter。

有没有带 GUI 的 jq 替代品?

有。fx 提供交互式 TUI。零安装的 GUI 选 浏览器 JSON Formatter,覆盖大多数”先看一眼再验证”的需求。Web 工具里 jqplay.org 直接就是 jq + 实时预览。

什么时候用 jq 而不是写 Python 脚本?

任务是一次性的、能装进 shell pipeline、且落在 filter/transform/extract 语义内,选 jq。需要单元测试、复杂状态、第三方库,或逻辑分支超出 .jq 文件可读性范围时,切到 Python。

在 jq 里怎么用正则表达式?

jq 通过 test("pattern")match("pattern")capture("pattern")scan("pattern") 暴露正则,统一用 PCRE 语法。第二个参数是 flag:test("abc"; "i") 忽略大小写。match 返回偏移量和 capture,scan 枚举所有不重叠匹配。

jq 输出的中文字符变成 \uXXXX 转义怎么办?

jq 默认直接输出 UTF-8 非 ASCII 字符——只要没加 -a / --ascii-output。如果看到 \u4e2d\u6587 这种转义,通常是终端 locale 未设为 UTF-8 或上游工具做了 ASCII 转义。确认 $LANGzh_CN.UTF-8en_US.UTF-8,并避免 -a flag。

核心外带

  1. 先抓心智模型:filter 进、0 个或多个 JSON 值出,用 | 组合。其他都是语法细节。
  2. 按任务学,不按操作符学:上面 30 个模式覆盖日常 jq 使用的 95%。
  3. 显式处理 null? 静默跳过,// default 具体回退。大多数 Cannot iterate over null 错误就用这两招修。
  4. 知道什么时候不该用 jq:单个响应交给浏览器 JSON Formatter;YAML 交给 yq;复杂逻辑交给真正的代码。
  5. 和现有工具组合jqcurlkubectlawsdocker、日志管道里最闪光。把它当粘合剂,不当逻辑层。

相关 JSON 工作流延伸阅读:JSON5 与 JSONC 格式化指南(配置文件语法扩展),CSV 与 JSON 互转指南(数据格式迁移里 jq 的位置)。JSON 里涉及时间戳时,Unix 时间戳完全指南覆盖了你转换日期字段时会遇到的各种坑。