使用while循环SSH到多个服务器


75

我有一个文件servers.txt,其中包含服务器列表:

server1.mydomain.com
server2.mydomain.com
server3.mydomain.com

当我逐行读取文件while并回显每一行时,所有文件均按预期工作。所有行均已打印。

$ while read HOST ; do echo $HOST ; done < servers.txt
server1.mydomain.com
server2.mydomain.com
server3.mydomain.com

但是,当我要SSH到所有服务器并执行命令时,突然我的while循环停止工作:

$ while read HOST ; do ssh $HOST "uname -a" ; done < servers.txt
Linux server1 2.6.30.4-1 #1 SMP Wed Aug 12 19:55:12 EDT 2009 i686 GNU/Linux

这仅连接到列表中的第一台服务器,而不连接到所有服务器。我不明白这里发生了什么。有人可以解释一下吗?

这甚至更陌生,因为使用for循环可以正常工作:

$ for HOST in $(cat servers.txt ) ; do ssh $HOST "uname -a" ; done
Linux server1 2.6.30.4-1 #1 SMP Wed Aug 12 19:55:12 EDT 2009 i686 GNU/Linux
Linux server2 2.6.30.4-1 #1 SMP Wed Aug 12 19:55:12 EDT 2009 i686 GNU/Linux
Linux server3 2.6.30.4-1 #1 SMP Wed Aug 12 19:55:12 EDT 2009 i686 GNU/Linux

它必须特定于ssh,因为其他命令可以正常工作,例如ping

$ while read HOST ; do ping -c 1 $HOST ; done < servers.txt

4
使用ansible
Matt

Answers:


98

ssh 正在读取其余的标准输入。

while read HOST ; do … ; done < servers.txt

read从标准输入读取。该<重定向从文件标准输入。

不幸的是,您尝试运行的命令也会读取stdin,因此它结束了文件的其余部分。您可以通过以下方式清楚地看到它:

$ while read HOST ; do echo start $HOST end; cat; done < servers.txt 
start server1.mydomain.com end
server2.mydomain.com
server3.mydomain.com

注意cat剩下的两行如何吃(和回声)。(已经按预期完成了阅读,每行在主机周围都有“开始”和“结束”。)

为什么for起作用?

您的for行不会重定向到stdin。(实际上,它servers.txt在第一次迭代之前将文件的全部内容读取到内存中)。因此,ssh继续从终端读取其标准输入(或可能不读取任何内容,具体取决于脚本的调用方式)。

至少在bash中,您可以read使用其他文件描述符。

while read -u10 HOST ; do ssh $HOST "uname -a" ; done 10< servers.txt
#          ^^^^                                       ^^

应该工作。10只是我选择的任意文件号。0、1和2具有定义的含义,通常,打开文件将从第一个可用数字开始(因此接下来将使用3)。因此,10足够高而不会被挡住,但是在某些壳中却足够低以至于低于极限。再加上一个不错的整数...

替代解决方案1:-n

正如McNisse他/她的答案中指出的那样,OpenSSH客户端具有-n阻止其读取stdin 的选项。这在的特定情况下效果很好ssh,但是当然其他命令可能缺少此功能-其他解决方案都可以工作,而不管哪个命令正在消耗您的stdin。

替代解决方案2:第二次重定向

您显然可以(例如,我尝试过,它至少在我的Bash版本中有效...)进行第二次重定向,如下所示:

while read HOST ; do ssh $HOST "uname -a" < /dev/null; done < servers.txt

您可以将其与任何命令一起使用,但是如果您确实希望终端输入进入该命令,则将很难。


1
数字10从哪里来?
Martin Vegter 2014年

2
@MartinVegter我弥补了。0/1/2是stdin,stdout和stderr。您可以选择任意一个数字,bash可以使您提高很多,可能达到操作系统限制。其他炮弹可能会限制您的数量...
derobert 2014年

我已经编辑过@MartinVegter,以回答您的两条评论。
derobert 2014年

另一种选择:exec 3<&0; while read HOST; do ssh $HOST "uname -a" <&3; done <servers.txt; exec 3<&- 这使文件描述符3成为原始stdin的备份,然后将其用于ssh的stdin,然后在完成后关闭备份FD。这适用于任何POSIX兼容的外壳。
理查德·汉森

8
-uPOSIX不支持该选项,因此不应将其用于#!/bin/sh脚本。使用read HOST <&10代替。另外,POSIX仅要求shell支持文件描述符0到9,因此10<servers.txt如果要严格遵守脚本,则不能使用POSIX 。
理查德·汉森

28

正如德罗伯特所描述的那样,ssh您会读到stdin

要更改此行为,您可以添加-n no ssh以防止其读取stdin。

ssh -n $HOST "uname -a"

10

你可能实际上是关闭使用更好的PSSH平行SSH项目。

pssh -h $hostfile -t $timeout -i $commands

-i表示互动。pssh还带有并行scp和并行rsync。很好的是它可以异步运行,并且可以根据您的要求运行尽可能多的线程。缺省值(非-i / interactive)是输出到stdout / stderr的单独目录,它通过$ outputdir / $ hostname进行输出。


3

如果您发现自己经常执行此类任务,则应尝试使用Fabric

按照说明安装Fabric ,大多数情况下只需要sudo apt-get install fabric

创建一个fabfile.py使用以下代码命名的文件:

from fabric.api import env, run

env.hosts = ['server1.mydomain.com',
             'server1.mydomain.com',
             'server1.mydomain.com']

def mytask():
    run('uname -a')

然后运行fab mytask将为您提供所需的结果。


1

使用这样的命令更容易:

for f in `cat servers.txt`; do ssh $f uname -a; done

我通常这样做:

for f in `cat servers.txt`; do echo "### $f ###"; ssh $f uname -a; done

echo是看哪个服务器被套牢,或无法连接到它。


1

因为ssh commnand从while语句提供的标准输入中获取所有流,

您可以使用管道将ssh的stdin切换到另一个源:

echo "" | ssh ...

例如:

while read HOST ; do echo "" | ssh $HOST "uname -a" ; done < servers.txt

while循环中所有ssh命令的stdin输入必须切换到另一个源。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.