暗无天日

=============>DarkSun的个人博客

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