使用jq遍历JSON数组失效问题排查与解决
你遇到的这个情况其实是bash的**单词拆分(Word Splitting)**在搞鬼!
为什么会出现拆分行为?
当你用$(echo "${conf}" | jq -r ".[]")把jq的输出作为for循环的遍历列表时,bash会默认把输出中的空格、换行符、制表符都当作分隔符,把整个输出拆成一个个独立的"单词"。而jq输出的每个JSON对象本身包含空格和换行(比如{ "fraudThreshold": 4, "fraudTTLSec": 60 }),所以bash就把完整的对象拆成了{、"fraudThreshold":、4,这类零碎的片段,自然每个片段都会单独打印成一行。
至于configy=$(echo $conf | jq .[-1])能正常获取最后一个元素,是因为这里是直接把jq的输出赋值给变量——bash会把输出里的换行换成空格,但整个JSON对象会作为一个完整的字符串存在变量中,没有触发for循环那种基于分隔符的拆分逻辑。
解决办法
这里给你几个靠谱的方案:
方案1:用jq的@sh格式化+while read循环
利用jq的@sh过滤器把每个JSON对象转成符合shell语法的引号包裹字符串,再用while read按行读取,避免拆分:
echo "${conf}" | jq -r '.[] | @sh' | while read -r configy; do echo "configy=$configy" done
@sh会自动把JSON对象用单引号包裹,转义内部的特殊字符,确保bash不会把对象内的空格当成分隔符;read -r则能完整读取每一行内容,不会处理反斜杠转义。
方案2:用bash数组存储元素(适合bash 4+)
先把每个JSON对象压缩成单行,再存到bash数组里,遍历数组时记得加引号避免拆分:
# 用jq -c把每个JSON对象压缩成单行,再存到数组 readarray -t configs < <(echo "${conf}" | jq -c '.[]') # 遍历数组时必须加引号,避免单词拆分 for configy in "${configs[@]}"; do echo "configy=$configy" done
jq -c是compact模式,会把每个JSON对象输出成一行;readarray -t则把每一行作为数组的一个元素,遍历数组时"${configs[@]}"的引号能保证每个元素被完整读取。
方案3:直接用jq -c+while read循环
如果不需要处理特殊字符,这个最简单:
echo "${conf}" | jq -c '.[]' | while read -r configy; do echo "configy=$configy" done
jq -c把每个JSON对象输出成单行,while read逐行读取,就能拿到完整的对象了。
内容的提问来源于stack exchange,提问作者user93726

