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

Mar*_*ter 95 scripting ssh io-redirection

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

server1.mydomain.com
server2.mydomain.com
server3.mydomain.com
Run Code Online (Sandbox Code Playgroud)

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

$ while read HOST ; do echo $HOST ; done < servers.txt
server1.mydomain.com
server2.mydomain.com
server3.mydomain.com
Run Code Online (Sandbox Code Playgroud)

但是,当我想通过 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
Run Code Online (Sandbox Code Playgroud)

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

这更奇怪,因为使用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
Run Code Online (Sandbox Code Playgroud)

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

$ while read HOST ; do ping -c 1 $HOST ; done < servers.txt
Run Code Online (Sandbox Code Playgroud)

der*_*ert 122

ssh 正在阅读标准输入的其余部分。

while read HOST ; do … ; done < servers.txt
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

注意cat其余两行是如何吃(和回声)的。(按照预期阅读完成,每一行都会在主机周围有“开始”和“结束”。)

为什么for有效?

您的for线路不会重定向到标准输入。(实际上,它servers.txt在第一次迭代之前将文件的全部内容读入内存)。所以ssh继续从终端读取它的标准输入(或者可能什么都不读取,取决于你的脚本是如何调用的)。

解决方案

至少在 bash 中,您可以read使用不同的文件描述符。

while read -u10 HOST ; do ssh $HOST "uname -a" ; done 10< servers.txt
#          ^^^^                                       ^^
Run Code Online (Sandbox Code Playgroud)

应该工作。10只是我选择的任意文件号。0、1 和 2 具有定义的含义,通常打开文件将从第一个可用数字开始(因此接下来使用 3)。因此,10 足够高以避开障碍,但又足够低以在某些炮弹中低于限制。加上它的一个不错的整数...

替代解决方案 1:-n

正如McNisse他/她的回答中指出的那样,OpenSSH 客户端有一个-n选项可以阻止它读取标准输入。这在 的特殊情况下效果很好ssh,但当然其他命令可能缺乏这一点 - 无论哪个命令正在吃你的标准输入,其他解决方案都可以工作。

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

您显然可以(就像我尝试过的那样,它至少在我的 Bash 版本中有效......)进行第二次重定向,看起来像这样:

while read HOST ; do ssh $HOST "uname -a" < /dev/null; done < servers.txt
Run Code Online (Sandbox Code Playgroud)

您可以将它与任何命令一起使用,但如果您真的希望终端输入进入命令,这将很困难。

  • POSIX 不支持`-u` 选项,因此不应用于`#!/bin/sh` 脚本;改用`read HOST &lt;&amp;10`。此外,POSIX 只要求 shell 支持文件描述符 0 到 9,因此如果脚本要严格遵守,则不能使用 `10&lt;servers.txt`。 (8认同)
  • @MartinVegter 我编的。0/1/2 是标准输入、标准输出和标准错误。你可以选择任何你喜欢的数字——bash 可以让你达到很高的水平,可能达到操作系统的限制。其他外壳可能会限制您使用更少的... (2认同)

小智 40

正如 derobert 描述的那样ssh读取您的stdin.

要更改此行为,您可以添加 -n no ssh 以防止它读取标准输入。

ssh -n $HOST "uname -a"
Run Code Online (Sandbox Code Playgroud)

  • 这个解决方案似乎比创建一个新的文件读取要好得多。 (2认同)

小智 11

实际上,您最好使用来自parallel-ssh项目的pssh

pssh -h $hostfile -t $timeout -i $commands
Run Code Online (Sandbox Code Playgroud)

-i意味着互动。pssh 还带有并行 scp 和并行 rsync。好处是它异步运行,并且会运行您要求的尽可能多的线程。默认(非 -i/interactive)是输出到 stdout/stderr 的单独目录,它由 $outputdir/$hostname 执行。