Tools

jq 命令实战指南:JSON 数据处理的瑞士军刀

✎ -- 字 🕐 -- 分钟
字号

jq 命令实战指南:JSON 数据处理的瑞士军刀

如果你经常在命令行里跟 JSON 打交道——无论是看 API 返回结果、分析日志、还是处理配置文件——你一定遇到过这种痛苦:对着满屏的 JSON 用 grep、sed、awk 拼凑提取逻辑,写出来的命令又臭又长,换个格式就全崩了。这时候你需要一把趁手的刀,而这把刀就是 jq

jq 是什么?官方定义很简洁:"jq is a lightweight and flexible command-line JSON processor." 翻译成人话就是:它是一个专门用来在命令行里处理 JSON 的瑞士军刀。可以过滤、映射、聚合、格式化、甚至做数学运算,而且语法极其灵活,一条命令就能完成你用 Python 写十几行脚本才能搞定的事。

本文从零开始,带你掌握 jq 的核心用法,从安装到生产实战,全程可复现。

一、安装与环境准备

jq 是一个单二进制文件,没有运行时依赖,安装非常简单:

macOS

brew install jq

Ubuntu / Debian

sudo apt update && sudo apt install jq -y

CentOS / RHEL

sudo yum install jq -y

Windows

winget install jqlang.jq
# 或者用 scoop
scoop install jq

验证安装

jq --version
# 输出类似:jq-1.7.1

装好之后,我们搞点测试数据。创建一个 data.json

{
  "name": "Alois Blog",
  "url": "https://alois.bond",
  "posts": [
    {
      "id": 1,
      "title": "jq 入门",
      "category": "tools",
      "tags": ["json", "cli", "devtools"],
      "views": 1024,
      "published": true,
      "created_at": "2026-06-15T10:30:00Z",
      "author": { "name": "Alois", "role": "admin" }
    },
    {
      "id": 2,
      "title": "Docker Compose 实战",
      "category": "devops",
      "tags": ["docker", "container", "devops"],
      "views": 3580,
      "published": true,
      "created_at": "2026-06-20T14:00:00Z",
      "author": { "name": "Alois", "role": "admin" }
    },
    {
      "id": 3,
      "title": "草稿:待发布文章",
      "category": "draft",
      "tags": [],
      "views": 0,
      "published": false,
      "created_at": "2026-06-28T08:00:00Z",
      "author": { "name": "Alois", "role": "admin" }
    }
  ],
  "stats": {
    "total_views": 4604,
    "total_posts": 3,
    "avg_views_per_post": 1534.67
  }
}

接下来所有例子都基于这份数据,你可以边看边试。

二、基础操作:从"看一眼"到"取出来"

2.1 格式化输出(最常用的命令)

如果你只学一个 jq 命令,那就是这个:

cat data.json | jq '.'

或者直接传文件:

jq '.' data.json

点号 . 是 jq 的身份过滤器(identity filter),它不做任何过滤,但会触发格式化。输出的 JSON 带语法高亮和缩进,比直接 cat 好看一百倍。

如果只需要紧凑输出:

jq -c '.' data.json    # compact,一行输出

如果只需要纯文本(不带引号):

jq -r '.name' data.json    # raw output
# 输出:Alois Blog(不带引号)

2.2 提取字段

提取顶层字段:

jq '.name' data.json
# 输出:"Alois Blog"

jq '.stats.total_views' data.json
# 输出:4604

提取数组元素:

# 取第一个元素(jq 索引从 0 开始)
jq '.posts[0]' data.json

# 取最后一个
jq '.posts[-1]' data.json

# 取前两个
jq '.posts[0:2]' data.json

2.3 管道操作

jq 的管道 | 跟 shell 管道很像,但它是在 jq 内部传递数据的:

# 先取 posts 数组,再取第一个元素,再取 title
jq '.posts | .[0] | .title' data.json
# 输出:"jq 入门"

这跟 .posts[0].title 等价,但管道写法在处理复杂转换时威力更大。

三、过滤器语法:jq 的真正灵魂

3.1 数组遍历与映射

.[] 是 jq 里最常用的操作之一——它把数组"拆开",每个元素单独输出:

# 遍历 posts 数组,输出每个 title
jq '.posts[].title' data.json
# 输出:
# "jq 入门"
# "Docker Compose 实战"
# "草稿:待发布文章"

结合 -r 拿到纯文本,可以直接喂给其他命令:

jq -r '.posts[].title' data.json | while read title; do
  echo "处理文章:$title"
done

3.2 条件过滤:select()

select() 是 jq 的 WHERE 子句,按条件筛选数组元素:

# 找出浏览量超过 2000 的文章
jq '.posts[] | select(.views > 2000)' data.json
# 找出已发布的文章标题
jq '.posts[] | select(.published == true) | .title' data.json
# 输出:
# "jq 入门"
# "Docker Compose 实战"
# 找出包含特定标签的文章
jq '.posts[] | select(.tags | contains(["docker"])) | .title' data.json
# 输出:"Docker Compose 实战"

常见陷阱select() 如果没匹配到任何元素,不会报错,只会静默返回空。这在脚本里可能造成困惑:

# 这个会返回空,不会报错
jq '.posts[] | select(.views > 99999) | .title' data.json

3.3 字段映射:map()

map() 对数组的每个元素做变换,返回新数组:

# 提取所有文章的 title,组成新数组
jq '.posts | map(.title)' data.json
# 输出:["jq 入门", "Docker Compose 实战", "草稿:待发布文章"]
# 给每篇文章加一个字段
jq '.posts | map(. + {site: "alois.bond"})' data.json

3.4 字段选择:提取子集

# 只取 id 和 title
jq '.posts[] | {id, title}' data.json
# 嵌套对象的字段
jq '.posts[] | {id, title, author_name: .author.name}' data.json

3.5 字符串与数学函数

jq 内置了丰富的函数:

# 字符串操作
jq '.name | length' data.json           # 长度:10
jq '.name | ascii_upcase' data.json     # 大写
jq '.name | startswith("Alois")' data.json  # true

# 数学运算
jq '.stats.total_views / .stats.total_posts' data.json
# 输出:1534.666...

jq '.stats.avg_views_per_post | floor' data.json
# 输出:1534

四、聚合与分组:把 jq 当 SQL 用

这是 jq 真正超越 grep+sed 的地方。面对 JSON 数组,你可以直接做聚合分析。

4.1 求和、平均、最大最小

# 总浏览量
jq '[.posts[].views] | add' data.json
# 输出:4604

# 平均浏览量
jq '[.posts[].views] | add / length' data.json

# 最大浏览量
jq '[.posts[].views] | max' data.json
# 输出:3580

4.2 按字段分组:group_by()

# 按 category 分组
jq '.posts | group_by(.category)' data.json

配合 map() 可以做出 SQL 风格的聚合结果:

# 每个分类的文章数和总浏览量
jq '.posts | group_by(.category) | map({
  category: .[0].category,
  count: length,
  total_views: (map(.views) | add)
})' data.json

输出:

[
  {"category": "devops", "count": 1, "total_views": 3580},
  {"category": "draft", "count": 1, "total_views": 0},
  {"category": "tools", "count": 1, "total_views": 1024}
]

这已经很像一条 SELECT category, COUNT(*), SUM(views) GROUP BY category 了。

4.3 排序:sort_by()

# 按浏览量升序
jq '.posts | sort_by(.views)' data.json

# 按浏览量降序
jq '.posts | sort_by(.views) | reverse' data.json

4.4 唯一值与去重

# 所有标签(展开后去重)
jq '[.posts[].tags[]] | unique' data.json
# 输出:["cli", "container", "devops", "devtools", "docker", "json"]

五、实战场景:5 个你一定会遇到的用法

场景 1:格式化 curl 返回的 API 数据

# 请求 GitHub API 并提取关键信息
curl -s https://api.github.com/repos/jqlang/jq | jq '{
  name,
  stars: .stargazers_count,
  forks: .forks_count,
  language,
  description
}'
# 只看最近 5 个 issue
curl -s https://api.github.com/repos/jqlang/jq/issues | jq '.[:5] | .[] | {
  number, title, user: .user.login, state
}'

场景 2:解析 Docker 输出

# 列出所有运行中容器的名称和端口
docker ps --format '{{json .}}' | jq -s 'map({
  name: .Names,
  image: .Image,
  ports: .Ports,
  status: .Status
})'
# 检查哪个容器占用了 80 端口
docker inspect $(docker ps -q) | jq '.[] | select(
  .NetworkSettings.Ports | to_entries[]?.value != null
) | {name: .Name[1:], ports: .NetworkSettings.Ports | keys}'

场景 3:处理 Nginx / 应用日志

假设你的应用输出 JSON 格式日志,每行一条记录(JSONL 格式):

# 统计每种状态码的数量
cat app.log | jq -r '.status' | sort | uniq -c | sort -rn

# 纯 jq 版本:统计 5xx 错误的请求路径
jq -r 'select(.status >= 500) | "\(.method) \(.path) [\(.status)]"' app.log

# 找出响应时间超过 1 秒的请求(假设字段是 response_time_ms)
jq 'select(.response_time_ms > 1000) | {path, time: .response_time_ms}' app.log

场景 4:处理 Kubernetes 输出

# 列出所有 Pod 的名称和状态
kubectl get pods -o json | jq '.items[] | {
  name: .metadata.name,
  status: .status.phase,
  node: .spec.nodeName,
  restarts: .status.containerStatuses[0].restartCount
}'
# 找出所有非 Running 状态的 Pod
kubectl get pods -o json | jq '.items[] | select(.status.phase != "Running") | .metadata.name'

场景 5:配置文件处理

# 读取 package.json 并提取依赖
jq '.dependencies | keys' package.json

# 检查某个依赖是否存在
jq '.devDependencies | has("typescript")' package.json

# 合并两个 JSON 配置文件
jq -s '.[0] * .[1]' config.json override.json

六、高级技巧:让 jq 飞起来

6.1 变量绑定:as

as 在管道中绑定变量,避免重复计算:

# 不用 as(重复取 length)
jq '.posts | (map(.views) | add) / length'

# 用 as 绑定(更清晰)
jq '.posts as $p | ($p | map(.views) | add) / ($p | length)' data.json

6.2 自定义函数

# 定义一个计算百分比的函数
jq 'def pct(a; b): (a / b * 100 | floor); .posts[] | {
  title,
  views,
  pct: pct(.views; 4604)
}' data.json

6.3 条件判断:if-then-else

jq '.posts[] | {
  title,
  views,
  level: (if .views > 2000 then "hot"
          elif .views > 500 then "normal"
          else "cold" end)
}' data.json

6.4 处理 null 值:alternative operator

# // 是 alternative operator,左边为 null/false 时用右边的值
jq '.posts[] | {title, tags: (.tags // ["untagged"])}' data.json

6.5 与外部变量交互:--arg

# 从 shell 变量传入 jq
TARGET_CATEGORY="devops"
jq --arg cat "$TARGET_CATEGORY" '.posts[] | select(.category == $cat) | .title' data.json
# 输出:"Docker Compose 实战"
# --argjson 用于传入 JSON 值(数字、布尔等)
MIN_VIEWS=2000
jq --argjson min "$MIN_VIEWS" '.posts[] | select(.views > $min) | .title' data.json

常见陷阱--arg 传入的值始终是字符串,比较数字时请用 --argjson

七、常用组合速查表

需求jq 命令
美化 JSONjq '.' file.json
紧凑输出jq -c '.' file.json
取字段(纯文本)jq -r '.field' file.json
遍历数组jq '.[] | .field' file.json
条件过滤jq '.[] | select(.field > 100)'
提取子集jq '.[] | {a, b}' file.json
数组映射jq 'map(.field)' file.json
数组求和jq '[.[] | .field] | add' file.json
按字段分组jq 'group_by(.field)' file.json
按字段排序jq 'sort_by(.field)' file.json
数组长度jq '.array | length' file.json
去重jq 'unique' file.json
合并多个文件jq -s '.[0] * .[1]' a.json b.json
传入外部变量jq --arg key "$val" '.[$key]'
只输出 key 名jq 'keys' file.json

八、性能注意事项

jq 本身是用 C 写的,速度很快,但面对超大文件时仍有几个需要注意的点:

  1. 用 streaming 模式处理大文件jq --stream 会把 JSON 拆成路径-值的流,内存占用极小。适合处理几 GB 的 JSON 文件。
  2. 避免多次遍历:如果要对同一个数组做多个聚合,用 as 绑定到变量,只遍历一次:
    # 不好:两次遍历
    jq '[.data[] | .price] | add' huge.json
    jq '[.data[] | .price] | length' huge.json
    
    # 好:一次遍历
    jq '.data as $d | {
      total: ($d | map(.price) | add),
      count: ($d | length)
    }' huge.json
  3. 管道中尽早过滤:把 select() 放在管道前面,减少后续操作处理的数据量。
  4. 用 -c 减少输出开销:紧凑模式不产生缩进和换行,在管道传递大量数据时更快。

九、写在最后

jq 是我日常使用频率最高的命令行工具之一。它不是银弹——遇到极其复杂的嵌套转换,你可能还是需要写 Python 脚本——但它覆盖了命令行下 90% 的 JSON 处理需求,而且语法简洁、性能出色。

几个学习建议:

  • jq '.' 开始,格式化输出本身就是最有用的功能。
  • jqplay.org 加入书签——这是一个在线的 jq 沙盒,可以实时预览过滤器效果,调试语法超级方便。
  • 熟记 5 个核心操作.(身份)、.[](展开数组)、select()(过滤)、map()(映射)、|(管道)。掌握了这 5 个,剩下的都是组合。
  • 读官方手册man jq 非常详尽,遇到不确定的用法直接查。

下次当你下意识地打开 Python 写一个 json.loads() + for 循环的脚本来处理 API 返回数据时,不妨先试试一行 jq。你可能会惊讶于它能省下多少时间。