Shell脚本高效生成JSON格式输出的完整指南
目录导读
- 为什么需要Shell脚本生成JSON?
- JSON基础与Shell兼容性分析
- 手动拼接JSON的陷阱与最佳实践
- 使用
jq命令:专业JSON处理利器 - 轻量级方案:
printf与echo安全技巧 - 处理特殊字符与转义逻辑
- 从结构化数据生成JSON数组
- 实战:API响应生成与日志格式化
- 常见错误排查与问答集锦
- 选择最适合你的方案
为什么需要Shell脚本生成JSON?
在微服务架构、DevOps自动化与云原生工具链中,JSON已成为数据交换的事实标准,Shell脚本作为服务器运维的核心工具,经常需要生成JSON格式的配置、API响应或日志,Shell对数据结构的原生支持薄弱,手动拼接JSON极易引入语法错误或安全漏洞。

核心场景:
- 自动化部署脚本生成配置JSON
- 监控系统采集指标后输出结构化数据
- CI/CD管道中生成测试报告
- 与REST API交互时构造请求体
JSON基础与Shell兼容性分析
JSON格式要求严格:
- 键必须用双引号包围
- 字符串值必须用双引号
- 不能有尾随逗号
- 特殊字符(如引号、换行符)需转义
Shell变量默认是字符串,其特殊字符(如空格、换行)会直接破坏JSON结构。
name="Bob's \"Tool\""
echo '{"name":"'$name'"}'
# 输出:{"name":"Bob's "Tool""} → 语法错误
这揭示了手动拼接的根本问题:Shell将双引号视为字符串边界,而JSON要求它们作为值的一部分。
手动拼接JSON的陷阱与最佳实践
致命陷阱
- 变量内嵌引号:
{$var}无法处理包含双引号的值 - 空格问题:
echo {"key":"value"}在严格模式下会报错 - 尾随逗号:循环输出数组时容易残留
- 多行值:换行符直接破坏JSON结构
最佳实践(仅限简单场景)
使用printf和%s格式化字符,避免Shell解释:
name="简单文本"
printf '{"name":"%s"}' "$name"
# 输出:{"name":"简单文本"}
但此方法仍无法处理值内部的引号或Unicode字符。
使用jq命令:专业JSON处理利器
jq是Linux下最强大的JSON处理器,它能从Shell变量、文件甚至管道生成合法JSON。
安装方式
# Ubuntu/Debian sudo apt-get install jq # CentOS/RHEL sudo yum install jq # macOS brew install jq # 验证安装 jq --version
基本生成模式
# 从字符串变量生成
name="Bob"
jq -n --arg name "$name" '{ "name": $name }'
# 输出:{"name":"Bob"}
# 从数值变量生成(无需引号)
age=30
jq -n --argjson age "$age" '{ "age": $age }'
# 从布尔值生成
is_active=true
jq -n --argjson active "$is_active" '{ "active": $active }'
# 多字段组合
jq -n \
--arg name "Alice" \
--argjson age 28 \
--argjson verified false \
'{ "user": $name, "age": $age, "verified": $verified }'
处理数组与嵌套
# 生成数组
items=("apple" "banana" "cherry")
jq -n --arg items "$(printf '%s\n' "${items[@]}")" ' $items | split("\n") '
# 更安全的数组传递:
jq -n --argjson items "$(printf '%s\n' "${items[@]}" | jq -R -s -c 'split("\n") | map(select(length > 0))')" '{ "fruits": $items }'
# 嵌套对象
jq -n \
--arg name "Server1" \
--arg ip "192.168.1.10" \
--argjson port 8080 \
'{
"host": $name,
"connection": {
"ip": $ip,
"port": $port
}
}'
为什么jq更安全?
- 自动处理所有特殊字符(引号、换行、反斜杠)
- 保证输出格式严格合法
- 支持
--arg和--argjson区分字符与数值类型 - 内置
@json过滤器确保值正确转义
轻量级方案:printf与echo安全技巧
当无法安装jq时,可以使用printf配合工具函数。
安全拼接函数
# 导出JSON字符串(自动转义双引号与反斜杠)
json_escape() {
local raw="$1"
# 转义反斜杠和双引号
printf '%s' "$raw" | sed 's/\\/\\\\/g; s/"/\\"/g'
}
# 生成键值对
build_json() {
local key="$1"
local value="$2"
printf '{"%s":"%s"}' "$key" "$(json_escape "$value")"
}
# 使用示例
name='Bob said "Hello"'
echo $(build_json "name" "$name")
# 输出:{"name":"Bob said \"Hello\""}
多行值处理
# 将多行文本转为JSON字符串
multiline_to_json() {
local raw
raw=$(cat)
printf '"%s"' "$(echo "$raw" | sed ':a;N;$!ba;s/\n/\\n/g; s/"/\\"/g')"
}
# 测试
echo 'Line1
Line2
Line3' | multiline_to_json
# 输出:"Line1\\nLine2\\nLine3"
数组生成
# 从空分隔列表生成JSON数组
array_to_json() {
local delimiter="$1"
shift
local items=("$@")
local json="["
local first=true
for item in "${items[@]}"; do
if $first; then
first=false
else
json+=","
fi
json+="\"$(json_escape "$item")\""
done
json+="]"
echo "$json"
}
# 使用
items=("apple" "banana" "cherry")
array_to_json "," "${items[@]}"
# 输出:["apple","banana","cherry"]
处理特殊字符与转义逻辑
JSON中的特殊字符包括:
- 双引号 →
- 反斜杠 →
- 换行符 →
\n - 制表符 →
\t - Unicode字符 →
\uXXXX
通用转义函数
strict_json_escape() {
local raw="$1"
printf '%s' "$raw" | \
sed 's/\\/\\\\/g;
s/"/\\"/g;
s/\t/\\t/g;
s/\r/\\r/g;
s/\n/\\n/g;
s/\x00//g;
s/\x08/\\b/g;
s/\x0c/\\f/g'
}
# 测试包含所有危险字符
dangerous="Tab\tNewline\nBackslash\\Quote\""
echo "{\"data\":\"$(strict_json_escape "$dangerous")\"}"
注意事项
- 永远不要使用
eval处理JSON,否则可能导致代码注入 - 在循环中拼接JSON时,优先使用临时文件或管道传递
- 对不可信输入(如用户输入、文件路径)务必转义
从结构化数据生成JSON数组
从CSV文件生成
# 假设data.csv内容:
# name,age,city
# Alice,28,New York
# Bob,35,Los Angeles
awk -F',' 'NR>1 {
printf "{ \"name\": \"%s\", \"age\": %s, \"city\": \"%s\" },\n", $1, $2, $3
}' data.csv | sed '$ s/,$//' | sed '1s/^/[/; $s/$/]/'
从命令输出生成
# 将ps命令输出转为JSON数组
ps aux | awk 'NR>1 {
user=$1; pid=$2; cpu=$3; mem=$4; command=$11;
printf "{\"user\":\"%s\",\"pid\":%s,\"cpu\":%s,\"mem\":%s,\"command\":\"%s\"},\n", user, pid, cpu, mem, command
}' | sed '$ s/,$//' | sed '1s/^/[/; $s/$/]/' | jq '.'
使用jq从文件直接加载
# 假设有user.txt,每行一个用户名
cat users.txt | jq -R -s -c 'split("\n") | map(select(length > 0)) | { "users": . }'
实战:API响应生成与日志格式化
场景:监控脚本生成健康检查JSON
#!/bin/bash
# 系统监控指标采集并输出JSON
# 采集数据
hostname=$(hostname)
uptime_seconds=$(cat /proc/uptime | awk '{print int($1)}')
memory_usage=$(free | grep Mem | awk '{printf "%.2f", $3/$2 * 100}')
cpu_load=$(uptime | awk -F'load average:' '{print $2}' | cut -d, -f1 | tr -d ' ')
# 使用jq构建最终JSON
jq -n \
--arg host "$hostname" \
--argjson uptime "$uptime_seconds" \
--argjson mem "$memory_usage" \
--argjson cpu "$cpu_load" \
--arg timestamp "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
'{
"type": "system_check",
"host": $host,
"timestamp": $timestamp,
"metrics": {
"uptime_seconds": $uptime,
"memory_percent": $mem,
"cpu_load_15m": $cpu
},
"status": (if $mem > 90 then "critical" elif $mem > 70 then "warning" else "ok" end)
}'
场景:日志行转为结构化JSON
# 假设日志格式:2024-01-15 ERROR Failed to connect
log_line="2024-01-15 ERROR Failed to connect"
# 解析并生成JSON
read -r date level message <<< "$log_line"
jq -n \
--arg date "$date" \
--arg level "$level" \
--arg msg "$message" \
'{ "timestamp": $date, "level": $level, "message": $msg }'
常见错误排查与问答集锦
Q1:为什么我的JSON总是缺少逗号或多出逗号?
A:最常见的原因是循环拼接时忘记处理边界,建议:
- 使用
jq的--slurp或--argjson自动管理数组 - 若手动拼接,先构建完整字符串再输出
- 在数组最后一个元素后不加逗号(使用
if first逻辑)
Q2:如何安全地将用户输入(如文件名)放入JSON?
A:永远使用jq的--arg传递字符串,它自动转义所有特殊字符,示例:
unsafe_filename="my file \"path\".txt"
jq -n --arg fname "$unsafe_filename" '{ "file": $fname }'
# 输出:{"file":"my file \"path\".txt"}
Q3:生成大型JSON数组时,内存不足怎么办?
A:使用流式处理。jq支持管道流:
# 逐行读取并输出JSON,最后用jq聚合
seq 1 100000 | xargs -I {} echo '{"index":{}}' | jq -s -c '.'
Q4:为什么我的JSON布尔值变成了字符串?
A:使用--argjson传递布尔值,而非--arg。--arg始终生成字符串:
# 错误
jq -n --arg is_ok true '{ "flag": $is_ok }'
# 输出:{"flag":"true"} → 字符串
# 正确
jq -n --argjson is_ok true '{ "flag": $is_ok }'
# 输出:{"flag":true} → 布尔值
Q5:如何测试我的JSON是否合法?
A:使用jq .格式化检查,或echo ... | python3 -m json.tool:
echo '{"name":"test"}' | jq . # 成功则输出格式化JSON
echo '{"name"}' | jq . # 失败则报错parse error
Q6:在shell脚本中如何复用JSON模板?
A:使用envsubst或jq的模板能力:
# 模板文件 template.jq
{
"service": $service_name,
"port": $port_number,
"config": {
"timeout": $timeout
}
}
# 调用时注入变量
jq -n --arg service_name "web" --argjson port_number 8080 --argjson timeout 30 -f template.jq
选择最适合你的方案
| 方案 | 适用场景 | 安全性 | 性能 | 可维护性 |
|---|---|---|---|---|
| jq命令 | 任何生产环境 | |||
| printf+转义函数 | 无jq的嵌入式环境 | |||
| heredoc直接拼接 | 极简单行输出 | |||
| awk/sed文本处理 | 从CSV/日志生成 |
黄金法则:只要有条件,优先使用jq,它由Stedolan开发,在GitHub有20k+星,是Shell生成JSON的事实标准,对于无法安装jq的场景,务必使用严格的转义函数,并避免将变量直接嵌入JSON字符串。
jq官网(访问stedolan.github.io/jq)提供了完整的教程和过滤器参考,强烈建议收藏。