Bash二维数组赋值异常问题求助
最近我遇到一个奇怪的脚本行为,至今搞不懂数组为什么会这样表现。
先贴一段脚本代码:
for nCDATAReg in $(seq 1 4) do sTestBuffer1=$(grep -Pzoi '\/\/<\!\[CDATA\[[\s\S]*?\/\/\]\]>' $SOMEFILE | sed -rz 's/\/\/<\!\[CDATA\[\n(.*)\/\/\]\]>/\1/g' | tr -d '\0' | sed -rz 's/InsertData\(([[:digit:]]+), [[:digit:]]+, \{(.*?)\}\)\;/\1/gm' | sed "${nCDATAReg}q;d") printf "\t\t\t\t\tDEBUG: Buffer is: $sTestBuffer1\t" aCDATA[0,$nCDATAReg]=$sTestBuffer1 sTestBuffer2=$(grep -Pzoi '\/\/<\!\[CDATA\[[\s\S]*?\/\/\]\]>' $SOMEFILE | sed -rz 's/\/\/<\!\[CDATA\[\n(.*)\/\/\]\]>/\1/g' | tr -d '\0' | sed -rz 's/InsertData\(([[:digit:]]+), [[:digit:]]+, \{(.*?)\}\)\;/\2/gm' | sed "${nCDATAReg}q;d") aCDATA[1,$nCDATAReg]=$sTestBuffer2 printf "and second Buffer is: $sTestBuffer2\t\n" # 注:原脚本里这里写的是$sTestBufferS,应该是笔误 printf "But array now contains: (${aCDATA[0,$nCDATAReg]}) and (${aCDATA[1,$nCDATAReg]})\n" done
对应的文件$SOMEFILE内容如下:
MORE TEXT ABOVE </script> <script>//<![CDATA[ InsertData(1, 1, {VALUE1}); InsertData(3, 1, {value2}); InsertData(6, 1, {vALUE3}); InsertData(91, 1, {Value4}); //]]></script> <div class="news-post news-post-style-full" MORE TEXT BELOW
理论上(控制台单独测试正则也没问题),这段代码应该把每个InsertData里的第一个数字和大括号里的内容分别提取出来,先存到变量里再赋值给数组的对应位置。变量里的值是对的,比如第一次循环sTestBuffer1是1,sTestBuffer2是VALUE1,但数组里两个位置却都变成了VALUE1。
实际输出如下:
DEBUG: Buffer is: 1 and second Buffer is: VALUE1 But array now contains: (VALUE1) and (VALUE1) DEBUG: Buffer is: 3 and second Buffer is: value2 But array now contains: (value2) and (value2) DEBUG: Buffer is: 6 and second Buffer is: vALUE3 But array now contains: (vALUE3) and (vALUE3) DEBUG: Buffer is: 91 and second Buffer is: Value4 But array now contains: (Value4) and (Value4)
我到底哪里弄错了?(除了明显的Bash基础薄弱之外😂)
问题根源分析
你遇到的核心问题是:Bash没有原生的二维数组,而且你用的下标写法被Bash当成了算术表达式解析!
在Bash中,普通的索引数组(默认的数组类型)只能用整数作为下标。当你写aCDATA[0,$nCDATAReg]时,Bash会把方括号里的内容当作算术表达式来计算。而算术表达式里的逗号是顺序求值运算符——它会执行所有子表达式,最后返回最后一个子表达式的值。
举个例子:
- 当
nCDATAReg=1时,0,$nCDATAReg会被解析成0,1,算术运算结果是1 - 同样,
1,$nCDATAReg解析成1,1,结果也是1
也就是说,你每次循环都是在给数组的同一个索引($nCDATAReg的值)赋值两次:第一次赋sTestBuffer1,第二次赋sTestBuffer2,所以最后数组里这个索引的值会被第二次的覆盖。而你访问的时候,两个下标表达式计算后都是同一个索引,自然取到的都是第二次赋值的内容。
解决方案
有两种常见的解决方式:
方式一:使用关联数组
关联数组支持用字符串作为下标,不会把下标当作算术表达式解析。只需要在使用数组前先声明它为关联数组:
# 在脚本开头、数组使用前添加这行 declare -A aCDATA
之后再用aCDATA["0,$nCDATAReg"]=$sTestBuffer1和aCDATA["1,$nCDATAReg"]=$sTestBuffer2赋值(引号可以省略,但加上更清晰),访问的时候用${aCDATA["0,$nCDATAReg"]}就能正确获取对应的值了。
方式二:用一维数组模拟二维数组
如果你不想用关联数组,可以把二维结构映射到一维数组里,比如按行或按列排列:
# 比如用索引 (nCDATAReg-1)*2 存第一个值,(nCDATAReg-1)*2+1 存第二个值 aCDATA[$(( (nCDATAReg - 1) * 2 ))]=$sTestBuffer1 aCDATA[$(( (nCDATAReg - 1) * 2 + 1 ))]=$sTestBuffer2 # 访问时对应取: printf "But array now contains: (${aCDATA[$(( (nCDATAReg - 1) * 2 ))]}) and (${aCDATA[$(( (nCDATAReg - 1) * 2 + 1 ))]})\n"
或者更直观一点,分开用两个一维数组,比如aCDATA_ID存数字部分,aCDATA_VALUE存大括号里的内容,这样逻辑更清晰,也不容易出错。
备注:内容来源于stack exchange,提问作者Talcott Parsons

