Jq 命令行 JSON 处理神器
jq 是一个轻量级且强大的命令行 JSON 处理器,类似于 sed 用于 JSON 数据。它允许你切片、过滤、映射和转换结构化数据,是处理 API 响应、配置文件和日志分析的必备工具。
安装
macOS
1brew install jq
Linux
1# Debian/Ubuntu
2sudo apt-get install jq
3
4# CentOS/RHEL
5sudo yum install jq
6
7# Alpine
8apk add jq
验证安装
1jq --version
2# jq 1.7
基本语法
jq 的基本用法:
1jq [options] 'filter' [file]
如果没有指定文件,jq 会从标准输入读取。
核心概念
1. 身份过滤器 (.)
最简单的过滤器,输出输入的内容不变:
1echo '{"name": "Alice", "age": 30}' | jq '.'
2# {
3# "name": "Alice",
4# "age": 30
5# }
2. 对象属性访问
使用 .key 访问对象属性:
1echo '{"name": "Alice", "age": 30}' | jq '.name'
2# "Alice"
3
4# 嵌套对象
5echo '{"user": {"name": "Alice", "age": 30}}' | jq '.user.name'
6# "Alice"
3. 数组操作
访问数组元素:
1echo '[1, 2, 3, 4, 5]' | jq '.[0]' # 第一个元素: 1
2echo '[1, 2, 3, 4, 5]' | jq '.[-1]' # 最后一个元素: 5
3echo '[1, 2, 3, 4, 5]' | jq '.[2:4]' # 切片: [3, 4]
4echo '[1, 2, 3, 4, 5]' | jq '.[:3]' # 前三个: [1, 2, 3]
4. 数组迭代 (.[])
遍历数组元素:
1echo '[{"name": "Alice"}, {"name": "Bob"}]' | jq '.[].name'
2# "Alice"
3# "Bob"
常用操作
过滤数据
使用 select() 条件过滤:
1echo '[{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]' | \
2 jq '.[] | select(.age > 28)'
3# {
4# "name": "Alice",
5# "age": 30
6# }
映射和转换
使用 map 转换数组:
1echo '[1, 2, 3]' | jq 'map(. * 2)'
2# [2, 4, 6]
3
4# 提取字段
5echo '[{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]' | \
6 jq 'map(.name)'
7# ["Alice", "Bob"]
创建新对象
使用 {} 构建新对象:
1echo '{"firstName": "Alice", "lastName": "Smith"}' | \
2 jq '{name: .firstName, full: "\(.firstName) \(.lastName)"}'
3# {
4# "name": "Alice",
5# "full": "Alice Smith"
6# }
数组操作
1# 排序
2echo '[3, 1, 4, 1, 5, 9]' | jq 'sort'
3# [1, 1, 3, 4, 5, 9]
4
5# 反转
6echo '[1, 2, 3]' | jq 'reverse'
7# [3, 2, 1]
8
9# 去重
10echo '[1, 1, 2, 2, 3]' | jq 'unique'
11# [1, 2, 3]
12
13# 分组
14echo '[{"a": 1}, {"a": 1}, {"a": 2}]' | jq 'group_by(.a)'
15# [[{"a": 1}, {"a": 1}], [{"a": 2}]]
16
17# 统计
18echo '[1, 2, 2, 3, 3, 3]' | jq 'group_by(.) | map({value: .[0], count: length})'
19# [{"value": 1, "count": 1}, {"value": 2, "count": 2}, {"value": 3, "count": 3}]
高级技巧
管道操作符 (|)
链式处理数据:
1echo '{"users": [{"name": "Alice", "active": true}, {"name": "Bob", "active": false}]}' | \
2 jq '.users | map(select(.active)) | map(.name)'
3# ["Alice"]
条件表达式
使用 if-then-else:
1echo '[1, 2, 3, 4, 5]' | jq 'map(if . % 2 == 0 then "even" else "odd" end)'
2# ["odd", "even", "odd", "even", "odd"]
字符串操作
1# 分割字符串
2echo '{"path": "/usr/local/bin"}' | jq '.path | split("/")'
3# ["", "usr", "local", "bin"]
4
5# 连接字符串
6echo '["a", "b", "c"]' | jq 'join("-")'
7# "a-b-c"
8
9# 大小写转换
10echo '{"name": "Alice"}' | jq '.name | ascii_downcase'
11# "alice"
数学运算
1echo '[1, 2, 3, 4, 5]' | jq 'add' # 求和: 15
2echo '[1, 2, 3, 4, 5]' | jq 'min' # 最小值: 1
3echo '[1, 2, 3, 4, 5]' | jq 'max' # 最大值: 5
4echo '[1, 2, 3, 4, 5]' | jq 'length' # 长度: 5
5echo '[1, 2, 3, 4, 5]' | jq 'unique | length' # 唯一值数量
处理 JSON 文件
1# 格式化 JSON
2jq '.' data.json
3
4# 压缩 JSON
5jq -c '.' data.json
6
7# 提取特定字段
8jq '.users[].name' data.json
9
10# 原地编辑(需要 -i 标志,但 jq 不支持,需用临时文件)
11tmp=$(mktemp)
12jq '.version = "2.0"' config.json > "$tmp" && mv "$tmp" config.json
实战案例
案例 1:处理 API 响应
1# 获取 GitHub 用户信息
2curl -s 'https://api.github.com/users/github' | \
3 jq '{login, name, public_repos, followers}'
4
5# {
6# "login": "github",
7# "name": "GitHub",
8# "public_repos": 158,
9# "followers": 28456
10# }
案例 2:分析日志文件
1# 统计各状态码出现次数
2cat access.log | jq -r '.status' | sort | uniq -c | sort -rn
3
4# 提取慢请求
5cat access.log | jq 'select(.duration > 1000) | {path: .request.path, duration}'
6
7# 提取错误日志
8cat app.log | jq 'select(.level == "error") | {timestamp, message}'
案例 3:转换数据格式
1# JSON 转 CSV
2echo '[{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]' | \
3 jq -r '(["name", "age"] | @csv), (.[] | [.name, .age] | @csv)'
4# "name","age"
5# "Alice",30
6# "Bob",25
7
8# CSV 转 JSON(需要配合其他工具)
9echo 'name,age
10Alice,30
11Bob,25' | python3 -c "import csv, json, sys; print(json.dumps([dict(r) for r in csv.DictReader(sys.stdin)]))" | jq '.'
12# [{"name": "Alice", "age": "30"}, {"name": "Bob", "age": "25"}]
案例 4:批量修改配置
1# 更新嵌套配置值
2jq '.database.connection.timeout = 30 | .database.connection.pool_size = 10' config.json
3
4# 合并配置
5jq -s '.[0] * .[1]' default.json override.json
6
7# 删除字段
8jq 'del(.unused_field)' config.json
案例 5:复杂过滤
1# 多条件过滤
2echo '[{"name": "Alice", "age": 30, "active": true},
3 {"name": "Bob", "age": 25, "active": false},
4 {"name": "Charlie", "age": 35, "active": true}]' | \
5 jq '[.[] | select(.active == true and .age >= 30)]'
6# [{"name": "Alice", "age": 30, "active": true}, {"name": "Charlie", "age": 35, "active": true}]
7
8# 按条件分组统计
9echo '[{"category": "A", "value": 10},
10 {"category": "A", "value": 20},
11 {"category": "B", "value": 15}]' | \
12 jq 'group_by(.category) | map({category: .[0].category, total: map(.value) | add})'
13# [{"category": "A", "total": 30}, {"category": "B", "total": 15}]
常用命令行选项
| 选项 | 说明 |
|---|---|
-c | 紧凑输出(单行) |
-r | 原始字符串输出(不带引号) |
-n | 使用 null 作为输入 |
-e | 如果结果为 false 或 null,退出码为 1 |
-f FILE | 从文件读取过滤器 |
-s | 读取所有输入到一个数组 |
--tab | 使用 tab 缩进 |
-S | 对象键排序输出 |
--color-output | 强制彩色输出 |
--monochrome-output | 禁用彩色输出 |
调试技巧
使用 debug 输出中间结果
1echo '[1, 2, 3]' | jq 'map(. * 2) | debug | map(. + 1)'
2# ["DEBUG:",[2,4,6]]
3# [3, 5, 7]
查看类型
1echo '["hello", 123, true, null, {"a": 1}]' | jq '.[] | type'
2# "string"
3# "number"
4# "boolean"
5# "null"
6# "object"
错误处理
1# 使用 ? 忽略错误
2echo '[1, "two", 3]' | jq '.[] | . * 2?'
3# 2
4# 6
5
6# 使用 try-catch
7echo '[1, "two", 3]' | jq '.[] | if type == "number" then . * 2 else "not a number" end'
8# 2
9# "not a number"
10# 6
性能优化
1# 使用 --stream 处理大文件
2jq --stream 'select(.[0][-1] == "id") | .[1]' large-file.json
3
4# 流式处理(逐行处理 JSON 数组)
5cat data.json | jq -c '.[]' | while read -r item; do
6 echo "$item" | jq '.name'
7done
快速参考
常用过滤器速查
1. # 输入本身
2.key # 对象属性
3.[index] # 数组索引
4.[] # 数组迭代
5| # 管道
6, # 多个输出
7[] # 数组构造器
8{} # 对象构造器
9select(bool) # 条件过滤
10map(f) # 数组映射
11sort # 排序
12unique # 去重
13group_by(f) # 分组
14add # 求和
15length # 长度
16keys # 获取键名
17values # 获取值
18to_entries # 对象转键值对数组
19from_entries # 键值对数组转对象
总结
jq 是处理 JSON 数据的瑞士军刀,掌握它可以大幅提升命令行数据处理效率。从简单的字段提取到复杂的数据转换,jq 都能优雅地处理。建议从基础命令开始练习,逐步掌握高级技巧,将其融入日常工作流程中。