jq 一行命令 在命令行中过滤和转换JSON,而无需编写脚本
七个实用的jq配方,专为后端开发人员和运维人员设计——涵盖select、map、to_entries、del、group_by以及实际API数据重塑。每个模式均展示输入和输出示例。
你在终端中看到的是一个压缩后的API响应。你需要提取3个字段,过滤掉空值,并将结果传递给其他程序。为此编写一个Python脚本需要20行代码和你没有的2分钟时间。 jq 它用一条命令就能完成——如果你知道这些模式的话。
以下是七个配方。每个配方都涵盖你在处理API响应或日志文件时会反复遇到的模式。每个配方都展示了输入、命令和输出。
安装
brew install jq # macOS
apt-get install jq # Ubuntu/Debian
apk add jq # Alpine (Docker images)
配方1:从数组中的每个对象中提取一个字段
你最常做的事情是 jq:从JSON数组中的每个项目中提取一个字段。
输入(users.json)
[
{"id": 1, "name": "Alice", "email": "alice@example.com", "role": "admin"},
{"id": 2, "name": "Bob", "email": "bob@example.com", "role": "user"},
{"id": 3, "name": "Carol", "email": "carol@example.com", "role": "user"}
]
jq '.[].name' users.json
# → "Alice"
# "Bob"
# "Carol" (newline-separated stream)
.[] 遍历数组中的每个元素; .name 提取该字段。输出是一个流——可用于传递给其他命令。如果你需要一个完整的JSON数组:
jq 'map(.name)' users.json
# → ["Alice", "Bob", "Carol"]
map(.name) 是的简写形式 [.[] | .name] ——它将表达式应用于每个项目,并将结果包裹在数组中。你会经常使用 map() 。
配方2:使用 select
仅保留满足条件的对象;丢弃其余部分。
jq 'map(select(.role == "admin"))' users.json
[
{"id": 1, "name": "Alice", "email": "alice@example.com", "role": "admin"}
]
select(expr) 仅当表达式为真时才传递项目——其余部分都会消失。将其包裹在 map() 中会保持输出为数组。更多示例:
# Numeric comparison
jq 'map(select(.score > 80))' results.json
# Not-null check
jq 'map(select(.error != null))' events.json
# String contains (requires test)
jq 'map(select(.message | test("timeout")))' logs.json
配方3:使用 map
仅选择你需要的字段,并在一次操作中可选地重命名它们。
jq 'map({name: .name, role: .role})' users.json
[
{"name": "Alice", "role": "admin"},
{"name": "Bob", "role": "user"},
{"name": "Carol", "role": "user"}
]
你正在为每个项目构建一个新的对象字面量。要重命名一个键,只需更改冒号左侧的部分:
jq 'map({username: .name, access_level: .role})' users.json
一个省略的快捷方式可以节省按键: {name} 等同于 {name: .name}。如果你想保留字段不变,就不必重复写键名:
jq 'map({id, name, role})' users.json
配方4:使用 to_entries 且 from_entries
map 在你知道键名存在的时候很有效。当你需要变换键名本身——添加前缀、转换命名约定或根据查找表重命名时—— to_entries 是正确的模式。
输入(config.json)
{"api_url": "https://api.example.com", "timeout": 30, "retry_count": 3}
# Add cfg_ prefix to every key
jq 'to_entries | map(.key = "cfg_" + .key) | from_entries' config.json
{"cfg_api_url": "https://api.example.com", "cfg_timeout": 30, "cfg_retry_count": 3}
to_entries 将 {"k": "v"} 进入 [{"key": "k", "value": "v"}]。在对结果数组中的条目进行修改后, from_entries 重构对象。要重命名一个特定的键而不影响其他键:
jq 'to_entries | map(if .key == "api_url" then .key = "endpoint" else . end) | from_entries' config.json
配方5:使用 del
与重塑相反:保留所有内容,移除少数特定字段。主要用途是在日志记录前或在传递给第三方服务前剥离敏感数据。
jq 'map(del(.email))' users.json
[
{"id": 1, "name": "Alice", "role": "admin"},
{"id": 2, "name": "Bob", "role": "user"},
{"id": 3, "name": "Carol", "role": "user"}
]
一次性删除多个字段,或删除嵌套路径:
# Multiple top-level fields at once
jq 'del(.password, .token, .refresh_token)' user.json
# Nested path
jq 'del(.user.internal_id)' response.json
# All fields named "debug" at any depth (recursive descent)
jq 'del(.. | .debug? // empty)' response.json
配方6:一次处理中过滤、重塑和排序
真正的好处是将这些基本操作链式组合。以下模式在处理分页API响应时经常出现:深入嵌套数组,过滤它,重塑对象,并对结果进行排序。
输入(repos.json)
{
"total_count": 3,
"items": [
{"id": 1, "name": "repo-alpha", "stargazers_count": 142, "language": "Go", "private": false},
{"id": 2, "name": "repo-beta", "stargazers_count": 89, "language": "Python", "private": true},
{"id": 3, "name": "repo-gamma", "stargazers_count": 310, "language": "Go", "private": false}
]
}
jq '.items
| map(select(.private == false and .language == "Go"))
| sort_by(-.stargazers_count)
| map({name, stars: .stargazers_count})' repos.json
[
{"name": "repo-gamma", "stars": 310},
{"name": "repo-alpha", "stars": 142}
]
该流程:
.items——深入嵌套数组map(select(...))——一次过滤;and连接条件sort_by(-.stargazers_count)——将值取反以实现降序排序;sort_by(.field)单独使用可实现升序map({name, stars: .stargazers_count})——最终重塑;{name}是的简写形式{name: .name}
配方7:使用 group_by
对结构化日志输出进行频率分析——无需数据库,无需awk复杂操作。
输入(logs.json)
[
{"level": "error", "msg": "connection timeout", "service": "auth"},
{"level": "info", "msg": "request received", "service": "api"},
{"level": "error", "msg": "null pointer exception", "service": "worker"},
{"level": "warn", "msg": "slow query detected", "service": "db"},
{"level": "error", "msg": "rate limit exceeded", "service": "api"}
]
jq 'group_by(.level) | map({level: .[0].level, count: length}) | sort_by(-.count)' logs.json
[
{"level": "error", "count": 3},
{"level": "info", "count": 1},
{"level": "warn", "count": 1}
]
group_by(.level) 返回一个数组的数组——每个子数组包含具有相同级别值的所有条目。 .[0].level 从组中的第一个项目中获取级别名称; length 统计该组中的条目数量。
在同一查询中添加按服务划分的详细信息:
jq 'group_by(.level) | map({
level: .[0].level,
count: length,
services: map(.service) | unique
})' logs.json
快速参考
上述模式以表格形式呈现,以便你需要快速提醒时使用:
| 你想要的功能 | jq 表达式 |
|---|---|
| 字段的所有值 | map(.field) |
| 筛选匹配项 | map(select(.field == "val")) |
| 重塑每个对象 | map({newKey: .oldKey}) |
| 重命名所有键 | to_entries | map(.key = ...) | from_entries |
| 移除字段 | map(del(.field1, .field2)) |
| 升序排序 | sort_by(.field) |
| 降序排序 | sort_by(-.field) |
| 按组计数 | group_by(.field) | map({key: .[0].field, count: length}) |
| 获取对象的所有键 | keys |
| 统计数组中的项目数量 | length |
| 字段的唯一值 | map(.field) | unique |
开始前:驯服数据块
API响应通常以压缩或深度嵌套的形式返回。如果你不确定需要的路径,可以将JSON粘贴到 JSON 格式化程序 ——它以可折叠节点的方式美化显示,以便你在编写 jq 表达式之前就能发现路径。在运行转换后, JSON 比较 工具在你重塑或移除字段时验证前后差异非常有用,以确保没有意外更改。
以上七个模式涵盖了你在命令行中进行的大部分JSON处理工作。真正强大的地方在于将它们链式组合——一旦你能进行过滤、重塑和排序,你就可以在不接触脚本文件的情况下处理大多数API响应。
