bash 中 while 循环的一个大坑
起因是这样的,我有一个常规的日志处理脚本,是最普通不过的 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))
,完全是个摆设。