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 配置;需要快速的浏览器 YAML↔JSON 互转,试试我们的 YAML 转 JSONJSON 转 YAML 工具
浏览器 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。

JSON Schema 校验呢?

jq 负责筛选和变形 JSON,但不强制 schema。当需要验证 API 响应、配置文件或消息体是否符合契约时,应改用 JSON Schema——参见 JSON Schema 校验完整指南,覆盖关键字、工具链与 CI 集成。

核心外带

  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 时间戳完全指南覆盖了你转换日期字段时会遇到的各种坑。

标签: json jq command-line devops developer-tools cli