记一次隐含子shell引发的问题
最近在写一个shell的时候发现了个奇怪的现象,有一个数组在 while 循环内是有内容的,然而出了 while 循环就又变成空值了.
代码示意如下:
declare -a files i=0 find ./ -name "d*.org" |while read file;do files[$i]="$file" i=$((i+1)) echo 1. count = ${#files[@]} done echo 2. count = ${#files[@]}
输出结果为:
1. count = 1 1. count = 2 2. count = 0
这就很神奇了. 在咨询了群里的大神之后才知道,原来当通过管道传送内容给一个循环中时,这个循环会隐式地在一个 子shell 中执行.
所以while中修改的其实是 子shell 中的 files. 而跳出这个 while 循环后,输出的是 父shell 中的 files.
其实这个问题可以通过 shellcheck 检测出来:
shellcheck /tmp/test.sh
结果为
n test.sh line 4:
find ./ -name "2*.org" |while read file;do
^-- SC2162: read without -r will mangle backslashes.
In test.sh line 5:
files[$i]="$file"
^-- SC2030: Modification of files is local (to subshell caused by pipeline).
In test.sh line 9:
echo 2. count = ${#files[@]}
^-- SC2031: files was modified in a subshell. That change might be lost.
正确的写法应该是:
declare -a files i=0 while read file;do files[$i]="$file" i=$((i+1)) echo 1. count = ${#files[@]} done< <(find ./ -name "d*.org") echo 2. count = ${#files[@]}
注意: done 后面不是 << (here document),而是 < <(command), 意思是将 command 的结果重定向给 while 循环.
