shell脚本执行冲突事件-ssh&while
今天发现一个问题
先看下脚本内容:
| Bash |
|---|
| # 首先目录下有两个.cfg文件
root@pts/0 # ls *.cfg
anaconda-ks.cfg original-ks.cfg
# 看脚本
root@pts/0 # cat test.sh
#!/bin/bash
ls *.cfg | tr ' ' '\n' | while read line
do
echo $line
if [ 'yes' == 'yes' ]
then
ssh 172.16.1.27 "echo 'yes'"
else
ssh 172.16.1.27 "echo 'no'"
fi
done
|
上面脚本很简单,读取当前目录的所有以cfg结尾的文件(实际上我只是取得循环次数,2次),但我们
运行脚本会发现脚本只循环一次就退出了。效果如下:
| Bash |
|---|
| root@pts/0 # bash -x test.sh
+ tr ' ' '\n'
+ ls anaconda-ks.cfg original-ks.cfg
+ read line
+ echo anaconda-ks.cfg
anaconda-ks.cfg
+ '[' yes == yes ']'
+ ssh 172.16.1.27 'echo '\''yes'\'''
yes
+ read line
|
此时恭喜你,你如愿接触到ssh和while 冲突事件了!
产生的原因
ssh 命令将while未执行的循环给读取了,下面内容可以帮助你理解。
| Bash |
|---|
| root@pts/0 # bash -x /tmp/test_nginx
+ ls 1.txt 2.txt ip.txt png.txt
+ tr ' ' '\n'
+ read line
+ echo 1.txt
1.txt
+ '[' yes == yes ']'
+ ssh 192.168.9.10 cat //我们将命令改成cat,就会发现了。
2.txt
ip.txt
png.txt
+ read line
|
ssh命令会从标准输入读取数据,而while循环中read line也是从标准输入读取,这样ssh会抢走while循环的输入,导致循环只执行一次
在脚本中:
| Bash |
|---|
| ls *.cfg | tr ' ' '\n' | while read line
|
这个管道会将两个文件名传递给while循环,但ssh命令默认会读取标准输入,当执行第一个ssh命令时,它会消耗掉管道中剩余的所有输入,导致 read 命令没有数据可读,循环提前结束
解决办法很简单
方法1:重定向 ssh 的输入
| Bash |
|---|
| ssh 172.16.1.27 "echo 'yes'" < /dev/null
|
方法2:使用 -n 选项禁用 ssh 的 stdin
| Bash |
|---|
| ssh -n 172.16.1.27 "echo 'yes'"
|
方法3:使用 for 循环替代 while 循环(推荐)
| Bash |
|---|
| #!/bin/bash
for line in *.cfg
do
echo "$line" # 防止文件名称有空格,使用引号
if [ 'yes' == 'yes' ]
then
ssh 172.16.1.27 "echo 'yes'"
else
ssh 172.16.1.27 "echo 'no'"
fi
done
|
- For循环不会有管道,避免管道和子shell的问题
- 不需要特殊处理标准输入
方案4:使用 process substitution
| Bash |
|---|
| #!/bin/bash
while read line
do
echo $line
if [ 'yes' == 'yes' ]
then
ssh 172.16.1.27 "echo 'yes'"
else
ssh 172.16.1.27 "echo 'no'"
fi
done < <(ls *.cfg)
|