如何在Bash脚本中使用jq高效构建大型JSON数组?
在Bash脚本里想用jq逐个添加元素构建JSON数组,元素是通过特定流程生成的。简化后的示例代码如下:
declare JSON_ARRAY=[] total_count=10000 OBJECT="{\"key_1\":1}" for i in $(seq 0 $total_count); do JSON_ARRAY=$(echo "$JSON_ARRAY" | jq .[$i]+="$OBJECT") done echo "$JSON_ARRAY" | jq -c
本来期望输出包含多个指定元素的JSON数组(比如3个元素时输出[{"key_1":1}, {"key_1":1}, {"key_1":1}]),但当元素数量到10000的时候,就会出现parse error或者Aborted (core dumped)的错误,推测是构建大数组时内存不够用。实际场景里,每个JSON对象元素都是在循环里通过build_object实时生成的,代码大概是这样:
for i in $(seq 0 $total_count); do OBJECT=$(build_object) JSON_ARRAY=$(echo "$JSON_ARRAY" | jq .[$i]+="$OBJECT") done
现在需要找个用jq实现这个需求的合理方法。
方法1:流式输出元素,最后用jq合并成数组
这是处理大量元素最高效的方式,完全避免了在内存里保存整个数组。思路是逐个输出合法的JSON元素,最后让jq一次性把所有元素打包成数组。
修改后的脚本:
total_count=10000 # 循环里逐个输出JSON元素,每行一个 for i in $(seq 0 $total_count); do # 这里替换成你的build_object命令,确保输出是合法的JSON对象 build_object done | jq -s .
如果build_object输出的JSON不是单行的,可以先通过jq -c格式化:
total_count=10000 for i in $(seq 0 $total_count); do build_object | jq -c . done | jq -s .
jq -s .会自动把输入的所有JSON元素收集到一个数组里,内存占用始终很低,不会因为元素数量多而爆炸。
方法2:用jq的reduce累加数组(适合小批量,不推荐10000+元素)
如果一定要在jq内部维护数组,可以通过变量传递的方式,避免每次重新解析整个数组字符串:
total_count=10000 OBJECT="{\"key_1\":1}" # 初始化为空数组,通过reduce循环添加元素 echo "[]" | jq --argjson obj "$OBJECT" -n ' $input as $arr | range(0; '$total_count'+1) | reduce . as $_ ( $arr; . + [$obj] ) '
不过这种方法对于10000+元素来说,内存占用还是比流式方法高,因为jq需要在内存里保存整个数组。
方法3:手动拼接数组字符串(需保证元素合法)
如果你的build_object输出的是完全合法的JSON对象,可以手动拼接数组格式,最后用jq修正可能的格式问题:
total_count=10000 echo -n "[" first=1 for i in $(seq 0 $total_count); do if [ $first -ne 1 ]; then echo -n "," fi build_object first=0 done echo "]" | jq -c .
这种方法需要确保每个元素都是合法JSON,最后用jq处理可能的格式错误(比如末尾多逗号),但效率还是不如流式方法。
为什么原来的方法不行?
原来的写法性能极差的核心原因是:每次循环都要把整个数组字符串传给jq解析、修改,再输出新的字符串。当数组越来越大时,每次解析和序列化的开销会指数级增长,最后直接把内存撑爆。而流式方法只需要处理单个元素,最后一次性合并,内存压力小很多。
内容的提问来源于stack exchange,提问作者sriganesh

