Shell脚本中,while循环使用ssh的坑(使用readline、ssh)
一、第一个坑
最近在写一个脚本,读取一个IP文件,遍历ssh后执行一些操作。但是很奇怪,永远在连上第一个IP以后,循环就结束了,不会对下面的IP进行遍历。
问题代码:
# $1是一个主机列表 cat $1 | while read LINE do echo "---------$LINE--------" ssh $LINE mv /tmp/test.txt /opt/ done
出现问题:
永远只会连接第一台机器,进行移动操作。列表中其余主机被略过。
问题原因:
while中使用重定向机制,$1 文件中的全部信息都已经读入并重定向给了while语句。所以当我们在while循环中再一次调用read语句,就会读取到下一条记录。问题就出在这里,ssh语句正好会读取输入中的所有东西,下面这个例子就能说明这个问题:
cat $1|while read LINE do echo "-------------$LINE---------" ssh 192.168.0.6 cat done
上面代码运行结果:
通过示例可以发现,ssh中的cat语句会打印出 $1 文件中的其他纪录,这就导致调用完ssh语句后,输入缓存中已经都被读完了,当read语句再读的时候当然也就读不到纪录,循环也就退出了。
如何解决?
1. 将ssh的输入重定向
cat $1|while read LINE do echo "-------------$LINE---------" ssh 192.168.0.6 ls < /dev/null done
2. 使用for循环实现
for LINE in `cat $1` do echo "-------------$LINE---------" ssh 192.168.0.6 cat done
3. 若坚持使用while循环,那么需要对ssh增加-n参数
为什么增加了-n参数也可以解决问题呢?通过man ssh查看-n参数的说明:
Redirects stdin from /dev/null (actually, prevents reading from stdin)
修改后的代码如下:
for LINE in `cat $1` do echo "-------------$LINE---------" ssh -n 192.168.0.6 cat done
http://www.yunweipai.com/archives/4339.html
二、第二个坑
起因是这样的,在一个常规的日志处理脚本中,最普通不过的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
运行后,很正常,在屏幕上打印
下面对脚本进行下修改,在while和do之间加上一个echo语句:
#!/bin/bash let sum=0 while((sum<10)) echo "nima" do echo $sum let sum++ done
运行之,死循环如约而至,屏幕上翻滚着:
#!/bin/bash let sum=0 while((sum<10)) echo "nima" do echo $sum let sum++ done
到这里,答案已经浮出水面了,问题不在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
当在循环中,sum增加到10后,再次执行while后面的语句,打印nima和niba,但执行[ $sum -le 10 ]时,返回码为非0.所以循环退出。而((sum<5)),完全是个摆设。