记一次隐含子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
循环.