不喜欢广告? 无广告 今天

jq 一行命令 在命令行中过滤和转换JSON,而无需编写脚本

更新于

七个实用的jq配方,专为后端开发人员和运维人员设计——涵盖select、map、to_entries、del、group_by以及实际API数据重塑。每个模式均展示输入和输出示例。

jq 一行命令:在命令行中过滤和转换JSON,而无需编写脚本 1
广告 移除?

你在终端中看到的是一个压缩后的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_entriesfrom_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响应。

想要享受无广告的体验吗? 立即无广告

安装我们的扩展

将 IO 工具添加到您最喜欢的浏览器,以便即时访问和更快地搜索

添加 Chrome 扩展程序 添加 边缘延伸 添加 Firefox 扩展 添加 Opera 扩展

记分板已到达!

记分板 是一种有趣的跟踪您游戏的方式,所有数据都存储在您的浏览器中。更多功能即将推出!

广告 移除?
广告 移除?
广告 移除?

新闻角 包含技术亮点

参与其中

帮助我们继续提供有价值的免费工具

给我买杯咖啡
广告 移除?