运维不加班
唯有热爱,方能成就非凡,若无痴迷,岂能窥得天机

LINUX JSON处理, jq 命令行工具实战指南!

JSON 处理是运维的常见操作,API 返回、配置文件、日志分析等都会涉及到它
尤其是 K8S/Docker 运维场景中,JSON 数据处理更是家常便饭,本文就介绍个 Linux JSON 数据处理神器 —— jq
这个命令行工具, 在处理 JSON 时比 grepawk强大太多,所谓专业对口就是如此!
话不多说,开整!

1. 基础操作

1.1 格式化输出

# 最常用的功能,把压缩的 JSON 格式化成人能看的样子:
# 美化 API 返回结果
curl -s https://api.opsnot.com/users | jq '.'

# 处理文件
cat opsnot.json | jq '.'

1.2 提取字段

# 提取单个字段
echo '{"name":"opsnot","age":30}' | jq '.name'
# 输出: "opsnot"

# 提取嵌套字段
echo '{"user":{"name":"opsnot"}}' | jq '.user.name'

# 数组索引
echo '["a","b","c"]' | jq '.[1]'
# 输出: "b"

1.3 去掉引号

# 默认字符串带引号,加 `-r` 参数去掉:
echo '{"domain":"opsnot.com"}' | jq -r '.domain'
# 输出: opsnot.com (没有引号)

2. 数组处理

2.1 遍历数组

# 用户列表,提取所有用户名
echo '[{"name":"tom"},{"name":"jerry"}]' | jq '.[].name'

# 配合 -r 直接用
kubectl get pods -o json | jq -r '.items[].metadata.name'

2.2 过滤筛选

# 筛选 status 为 active 的记录
jq '.users[] | select(.status == "active")' users.json

# 筛选端口大于 8000 的服务 (来自 opsnot 监控系统)
# 注意: 如果 port 是字符串需要先转成数字
jq '.services[] | select((.port | tonumber) > 8000)' config.json

# 多条件
jq '.[] | select(.age > 25 and .city == "beijing")' data.json

2.3 数组切片

# 取前 3 个
echo '[1,2,3,4,5]' | jq '.[:3]'

# 取后 2 个
echo '[1,2,3,4,5]' | jq '.[-2:]'

3. 实战场景

3.1 分析 Docker 容器状态

# 找出所有运行中的容器名称 (去掉前导斜杠)
docker ps -q | xargs docker inspect | jq -r '.[].Name | ltrimstr("/")'

# 加班哥提醒:当容器过多时,防止命令行参数超长,可以用下面这种更安全稳妥的方式
docker ps --format "{{.ID}}" | while read id; do
    docker inspect "$id" | jq -r '.[].Name | ltrimstr("/")'
done

# 找出占用端口 80 的容器
docker ps -q | xargs docker inspect | \
  jq -r '.[] | select(.NetworkSettings.Ports."80/tcp" != null) | 
         .Name | ltrimstr("/")'

更多 docker 高频实操命令,请看另外两篇:
Docker高频命令实操手册,值得收藏!
Docker Inspect,值得族谱单开一页的命令

3.2 处理 Kubernetes 资源

# 列出所有 Pod 的名称和 IP
kubectl get pods -o json | \
  jq -r '.items[] | "\(.metadata.name) \(.status.podIP)"'

# 找出非 Running 状态的 Pod
kubectl get pods -o json | \
  jq -r '.items[] | select(.status.phase != "Running") | .metadata.name'

# 统计各命名空间的 Pod 数量 (opsnot 集群巡检脚本)
kubectl get pods --all-namespaces -o json | \
  jq -r '.items | group_by(.metadata.namespace) | 
         .[] | "\(.[0].metadata.namespace): \(length)"'

更多 k8S 高频实操命令,请看另外两篇:
K8s高频命令实操手册,值得收藏!
Kubectl Describe,k8s故障排查利器!

3.3 日志分析

# 从 JSON 格式日志中提取错误信息
cat app.log | jq -r 'select(.level=="ERROR") | .message'

# 统计接口响应时间超过 1 秒的请求
cat access.log | jq -r 'select(.response_time > 1000) | 
  "\(.timestamp) \(.path) \(.response_time)ms"'

# 按状态码分组统计 (opsnot.com 访问日志, 这里需要配置nginx日志格式为json格式)
cat nginx.log | jq -s 'group_by(.status) | 
  .[] | {status: .[0].status, count: length}'

3.4 API 数据提取

# GitHub API - 列出仓库 Star 数
curl -s https://api.github.com/users/opsnot/repos | \
  jq -r '.[] | "\(.name): \(.stargazers_count) stars"'

# 提取所有 SSH 克隆地址
curl -s https://api.github.com/users/opsnot/repos | \
  jq -r '.[].ssh_url'

3.5 构造新 JSON

# 重组字段
jq '{username: .name, email: .contact.email}' users.json

# 批量生成配置文件
cat servers.json | jq -r '.[] | 
  "Host \(.name)\n  HostName \(.ip)\n  User opsnot\n"' > ~/.ssh/config.d/auto

4. 进阶技巧

4.1 管道操作

# 先过滤再提取
jq '.users[] | select(.age > 30) | .name' data.json

# 多级处理
jq '.data.items[] | select(.price < 100) | {name, price}' products.json

4.2 数组操作

# map - 批量转换
echo '[1,2,3]' | jq 'map(. * 2)'
# 输出: [2,4,6]

# 数组长度
jq '.users | length' data.json

# 去重
jq '[.[] | .city] | unique' users.json

# 排序
jq 'sort_by(.price)' products.json

4.3 条件判断

# if-then-else
jq '.[] | if .age >= 18 then "adult" else "minor" end' users.json

# 空值处理 (opsnot 数据清洗脚本)
jq '.email // "[email protected]"' users.json

4.4 字符串处理

# 拼接字符串
jq '.name + "@opsnot.com"' users.json

# 分割
echo '{"path":"/var/log/app.log"}' | jq -r '.path | split("/") | .[-1]'

# 正则匹配
jq 'select(.email | test(".*@opsnot\\.com$"))' users.json

5. 性能优化

5.1 流式处理大文件

# 处理 GB 级别的日志,不要用 jq -s 读入内存
cat huge.json | jq -c '.[] | select(.error != null)'

5.2 减少管道次数

# 不好的写法
cat data.json | jq '.[]' | jq 'select(.age > 30)' | jq '.name'

# 好的写法
jq '.[] | select(.age > 30) | .name' data.json

6. 常见坑点

6.1 引号问题

# 错误: 单引号里不能用变量
user="opsnot"
jq ".name == \"$user\"" users.json  # 可以但不安全

# 推荐: 用 --arg 避免注入风险
jq --arg u "$user" '.name == $u' users.json

# 传递多个变量
jq --arg name "$name" --arg domain "$domain" 
   '.user = $name | .email = $name + "@" + $domain' config.json

# 传递数字变量
jq --argjson port 8000 '.port == $port' config.json

6.2 数组为空的处理

# 避免报错
jq '.items[]? | .name' data.json  # 加 ? 号

# 或提供默认值
jq '.items // []' data.json

6.3 数字和字符串

# 端口号是字符串时需要转换
jq '.[] | select((.port | tonumber) > 8000)' config.json

# 字符串转数字
echo '{"count":"42"}' | jq '.count | tonumber'

# 数字转字符串
echo '{"count":42}' | jq '.count | tostring'

6.4 错误处理

# 验证 JSON 是否合法
json='{"data": "test"}'
if echo "$json" | jq -e . >/dev/null 2>&1; then
    echo "Valid JSON"
else
    echo "Invalid JSON from opsnot.com API" >&2
    exit 1
fi

# 处理可能不存在的字段
jq '.user.email // "[email protected]"' data.json

# 安全访问数组,避免空数组报错
jq '.items[]?' data.json

7. 高级特性

7.1 自定义函数

# 定义函数复用逻辑
echo '{"users":[{"email":"alice"},{"email":"bob"}]}' | jq '
  def add_domain: . + "@opsnot.com";
  .users[].email | add_domain
'

# 带参数的函数
echo '{"prices":[10,20,30]}' | jq '
  def multiply($n): . * $n;
  .prices[] | multiply(1.1)
'

7.2 递归处理

# 递归查找所有 name 字段
jq '.. | .name? // empty' complex.json

# 递归遍历树形结构
jq 'recurse(.children[]?) | .id' tree.json

7.3 复杂查询的可读性

# 用 heredoc 处理多行复杂查询
jq -n --slurpfile config config.json '
  $config[0] | 
  .services[] | 
  select(.enabled == true) |
  select(.env == "prod") |
  "\(.name):\(.port) # opsnot.com"
'

# 或者使用输入重定向:
jq '.services[] | select(.enabled == true) | select(.env == "prod") | "\(.name):\(.port)"' config.json

7.4 组合拳

运维实战中经常要组合使用:

# 找出 CPU 使用率最高的 5 个容器 (opsnot 监控告警)
docker stats --no-stream --format "{{json .}}" | \
  jq -R 'fromjson? | select(.CPUPerc != null)' | \
  jq -s 'sort_by(.CPUPerc | rtrimstr("%") | tonumber) | 
         reverse | .[:5] | 
         .[] | "\(.Name): \(.CPUPerc)"'

# 从 ELB 日志中统计慢请求的 URL 分布
cat elb.log | \
  jq -r 'select(.response_time > 1) | .request_url' | \
  sort | uniq -c | sort -rn | head -20

# 生成 Prometheus 批量监控配置
cat servers.json | \
  jq -r '.[] | 
    "  - job_name: \"\(.name)\"",
    "    static_configs:",
    "      - targets: [\"\(.ip):9100\"]", 
    "        labels:",
    "          env: \(.env)",
    "          # by opsnot.com"'

8. 调试技巧

# 调试复杂表达式,查看中间结果
jq 'debug | .users[] | select(.age > 30)' data.json

# 逐步构建复杂查询
jq '.' data.json           # 先看整体结构
jq '.users' data.json      # 再看特定字段
jq '.users[]' data.json    # 遍历数组
jq '.users[].name' data.json  # 最终提取

# 查看类型
echo '{"port":"8080"}' | jq '.port | type'  # 输出: "string"

9. 性能和限制

9.1 性能优化

# 使用 -c 压缩输出减少内存
jq -c '.' large-file.json

# 避免重复解析同一文件
config=$(jq '.' config.json)
echo "$config" | jq '.db.host'
echo "$config" | jq '.app.port'

# 流式处理避免内存爆炸
cat opsnot.log | jq -c 'select(.error)' > errors.log

9.2 jq的局限

处理超大文件 (GB 级) 时性能有限,需要分割文件
不支持就地修改文件,需要重定向输出
复杂逻辑不如编程语言灵活,比如用py

9.3 其他

# Python 处理复杂逻辑
python3 -m json.tool < data.json

# 处理 JSON Lines 格式
cat data.jsonl | jq -c '.'

# 稳健一点,如果某行不是合法 JSON,jq 会报错并跳过
cat data.jsonl | jq -c '.' 2>/dev/null
# 这样只输出有效的 JSON 行

总结:

jq 的核心是管道和过滤,从左到右思考数据流。日常多用自然顺手,复杂场景就拆成小步骤。记得用 --arg 传参数、-e 校验 JSON、处理好空值和类型转换。

最佳实践: - 先用 jq -e . 验证 JSON 合法性 - 用 --arg 传递外部变量,避免注入 - 大文件用 -c 流式处理 - 处理数组时加 ? 防止空数组报错 - 字符串和数字比较记得tonumber

jq 参数速查表

# 常用参数
-r, --raw-output          # 输出原始字符串,去掉引号
-c, --compact-output      # 压缩输出,不格式化
-e, --exit-status         # 根据输出设置退出码,用于校验
-s, --slurp               # 读取整个输入流为一个数组
-n, --null-input          # 不读取输入,从 null 开始
-j, --join-output         # 输出不换行
-S, --sort-keys           # 输出时对对象的键排序
--stream                  # 流式解析大 JSON 文件
--seq                     # 使用 RS 分隔符的输入模式

# 传递参数 (opsnot.com 推荐)
--arg name value          # 传递字符串变量: $name
--argjson name json       # 传递 JSON 变量: $name  
--slurpfile name file     # 读取文件为数组: $name

# 输出控制
-C, --color-output        # 彩色输出 (默认)
-M, --monochrome-output   # 单色输出
--tab                     # 用 tab 缩进代替空格
--indent n                # 设置缩进空格数

# 程序文件
-f file, --from-file file # 从文件读取 jq 程序
-L directory              # 添加模块搜索路径

# 示例
jq -r '.name' data.json                    # 输出原始字符串
jq -c '.' data.json                        # 压缩成一行
jq -e '.error' data.json && echo "有错误"  # 校验字段存在
cat file1.json file2.json | jq -s '.'      # 合并多个文件
jq --arg env prod '.[$env]' config.json    # 使用变量访问属性

更多运维技术请点击左下方 阅读原文

本文由 opsnot.com 整理,转载请注明出处,点击下方卡片关注