从一个bash "bug"说起

两个例子

假设文件vms里存着两个vm的名称:

vm1
vm2

那么你期待下面的脚本while_loop.sh会输出什么?

#!/bin/bash
# while_loop.sh
while read vm; do
  echo "$vm"
  ssh root@${vm} "sleep 2"
done < vms

结果像下面这样:看起来vm2的结果根本就没有执行!!

vm1

那么再来看看下面这个脚本for_loop.sh

#!/bin/bash
# for_loop.sh
vms=(vm1 vm2)

for vm in ${vms[@]}; do
  echo "$vm"
  ssh root@${vm} "sleep 2"
done

它和上面的脚本不同之处只是把while循环换成了for循环,但是输出结果一切正常:

vm1
vm2

当我碰到这个问题时,表情一开始是这样的: Alt text

研究了一番,发现根本问题在于:ssh命令默认把当前的标准输入(很不巧,在while循环中,当前的标准输入是vms文件中第一行之后的所有内容)作为了远程执行命令的标准输入——尽管在这个例子中,sleep命令根本不需要标准输入。

那么,我们用cat命令来模拟一下远程命令需要标准输入的情况:

#!/bin/bash
# while_loop_1.sh
while read vm; do
  echo "###$vm###"
  ssh root@${vm} "cat"
done < vms

输出结果跟预想的一样:

###vm1###
vm2

知道了原因,怎么来改这个程序就很容易了,有三种方法:

  1. while中的标准输入重定向至/dev/null:
    0</dev/null ssh root@${vm} "sleep 2"
  2. /dev/null来作为标准输入:
    ssh root@${vm} "sleep 2" < /dev/null
  3. 使用ssh -n:
    ssh -n root@${vm} "sleep 2"

    -n Redirects stdin from /dev/null (actually, prevents reading from stdin).

Refs:

  • http://www.unix.com/shell-programming-and-scripting/38060-ssh-break-while-loop.html
  • http://novosial.org/shell/while-ssh/index.html
  • http://linux.spiney.org/help_ssh_eating_all_my_standard_input