起因是这样的,我有一个常规的日志处理脚本,是最普通不过的 while read line;do XXXX;done<file 的应用场景。可是发现文件处理完后,该脚本并没有停止,仍在不停执行,准确点说,是死循环了。第一反应是想到是不是文件格式问题,导致在判断文件结束上出现了问题?但所有的文件都是在服务器上直接生成或创建的,不会存在这个问题。脚本通读了几遍,未果;无奈之下,只好祭出 bash -x 来。才发现,原来是在敲脚本时,不知怎么手抖了一下,在 while 和 do 语句之间,打上了个 echo 语句。这个就是罪魁祸首了,删掉后,脚本就恢复正常了。

如果就这么过去了,多没意思,我觉得有必要深究一下 while 的运行机制。

假设 while_test.sh 脚本内容如下:

#!/bin/bash  
  
let sum=0
while((sum<10))  
do  
echo $sum  
let sum++  
done  

运行后,很正常,在屏幕上打印:

0  
1  
2  
3  
4  
5  
6  
7  
8  
9  

下面对脚本进行下修改,在 while 和 do 之间加上一个 echo 语句:

#!/bin/bash  
  
let sum=0  
while((sum<10))  
echo "nima"  
do  
echo $sum  
let sum++  
done  

运行之,死循环如约而至,屏幕上翻滚着:

.....(略)......  
nima  
1779  
nima  
1780  
nima  
1781  
nima  
1782  
nima  
1783  
nima  
1784  
nima  
1785  
nima  
1786  
nima  
1787  
nima  
1788  
......(略)......  

到这里,答案已经浮出水面了,问题不在 bash,而在于受 C 语言的影响,我们习惯性的认为 while 应该把 sum<10 作为循环进行的条件。但 bash 里,碰到 while 后,它会把 while 到 do 之间的所有语句的执行结果作为循环进行的条件,而所谓“所有语句的执行结果”,就是这所有语句中,最后一个语句的执行结果。如果这最后一个语句执行成功,则继续执行 do 到 done 之间的内容,循环继续;如果执行失败,则退出循环。按照 shell 的惯例,执行成功与否,可以根据返回码来判断,返回码为 0,则成功,非 0 则失败。

验证一下,将脚本改为如下:

#!/bin/bash  
  
let sum=0  
while((sum<5))  
echo "nima"  
echo "niba"  
[ $sum -le 10 ]  
do  
echo $sum  
let sum++  
done  

执行之,在屏幕上打印:

nima  
niba  
0  
nima  
niba  
1  
nima  
niba  
2  
nima  
niba  
3  
nima  
niba  
4  
nima  
niba  
5  
nima  
niba  
6  
nima  
niba  
7  
nima  
niba  
8  
nima  
niba  
9  
nima  
niba  
10  
nima  
niba  

当在循环中,sum 增加到 10 后,再次执行 while 后面的语句,打印 nima 和 niba,但执行 [ $sum -le 10 ] 时,返回码为非 0。所以循环退出。而 ((sum<5)),完全是个摆设。