使用 bash 从文件中读取行:for vs. while

Ken*_*ert 38 bash io-redirection files

我正在尝试使用 bash 脚本读取文本文件并对每一行执行某些操作。

所以,我有一个看起来像这样的列表:

server1
server2
server3
server4
Run Code Online (Sandbox Code Playgroud)

我想我可以使用 while 循环来循环,如下所示:

while read server; do
  ssh $server "uname -a"
done < /home/kenny/list_of_servers.txt
Run Code Online (Sandbox Code Playgroud)

while 循环在 1 次运行后停止,因此只uname -a在 server1 上运行

但是,使用 cat 的 for 循环可以正常工作:

for server in $(cat /home/kenny/list_of_servers.txt) ; do
  ssh $server "uname -a"
done
Run Code Online (Sandbox Code Playgroud)

更令我困惑的是,这也有效:

while read server; do
  echo $server
done < /home/kenny/list_of_servers.txt
Run Code Online (Sandbox Code Playgroud)

为什么我的第一个示例在第一次迭代后停止?

Gil*_*il' 29

for循环是在这里很好。但请注意,这是因为该文件包含机器名称,其中不包含任何空白字符或通配符。for x in $(cat file); do …通常无法遍历 的行file,因为 shell 首先cat file在有空格的任何地方拆分命令的输出,然后将每个单词视为全局模式,以便\[?*进一步扩展。for x in $(cat file)如果你努力,你可以确保安全:

set -f
IFS='
'
for x in $(cat file); do …
Run Code Online (Sandbox Code Playgroud)

相关阅读:遍历名称中带有空格的文件?; 如何从bash中的变量逐行读取?; 为什么while IFS= read经常使用,而不是IFS=; while read..请注意,在使用时while read,读取行的安全语法是while IFS= read -r line; do ….

现在让我们看看您的while read尝试出了什么问题。来自服务器列表文件的重定向适用于整个循环。所以当ssh运行时,它的标准输入来自那个文件。ssh 客户端无法知道远程应用程序何时可能想要读取其标准输入。因此,一旦 ssh 客户端注意到一些输入,它就会将该输入发送到远程端。如果需要,那里的 ssh 服务器准备好将该输入提供给远程命令。在您的情况下,远程命令从不读取任何输入,因此数据最终会被丢弃,但客户端对此一无所知。您的尝试echo有效,因为它echo从不读取任何输入,而是单独保留其标准输入。

有几种方法可以避免这种情况。您可以使用-n选项告诉 ssh 不要从标准输入中读取数据。

while read server; do
  ssh -n $server "uname -a"
done < /home/kenny/list_of_servers.txt
Run Code Online (Sandbox Code Playgroud)

-n选项实际上告诉ssh将其输入从/dev/null. 您可以在 shell 级别执行此操作,它适用于任何命令。

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

避免来自文件的 ssh 输入的一种诱人方法是将重定向放在read命令上:while read server </home/kenny/list_of_servers.txt; do …. 这将不起作用,因为它会导致每次read执行命令时再次打开文件(因此它会一遍又一遍地读取文件的第一行)。重定向需要在整个 while 循环中进行,以便在循环期间打开文件一次。

一般的解决方案是在标准输入之外的文件描述符上为循环提供输入。Shell 具有将输入和输出从一个描述符编号传送到另一个描述符编号的构造。在这里,我们打开文件描述符 3 上的文件,并read从文件描述符 3 重定向命令的标准输入。 ssh 客户端忽略打开的非标准描述符,所以一切正常。

while read server <&3; do
  ssh $server "uname -a"
done 3</home/kenny/list_of_servers.txt
Run Code Online (Sandbox Code Playgroud)

在 bash 中,该read命令具有从不同文件描述符读取的特定选项,因此您可以编写read -u3 server.

相关阅读:文件描述符和 shell 脚本你什么时候会使用额外的文件描述符?

  • 伙计,这个堆栈交换肯定与服务器故障不同。这里的人们真的竭尽全力不仅回答,而且教育。非常感谢。 (8认同)

l0b*_*0b0 26

您应该使用while,而不是for。避免命令在这样的循环中吞下标准输入的方法就是使用另一个文件描述符

while read -u 9 server; do
  ssh $server "uname -a"
done 9< /home/kenny/list_of_servers.txt
Run Code Online (Sandbox Code Playgroud)

有关更多信息,help [r]ead真的)和另一篇解释原因的文章

  • 或者,`help read` - `help` 是获取 shell 内置程序的等同于 `man` 页的命令。 (6认同)

man*_*ork 22

在您的第一个代码中,ssh将从while. 添加-n选项ssh来避免它。来自man ssh

-n     Redirects stdin from /dev/null (actually, prevents reading from stdin).
Run Code Online (Sandbox Code Playgroud)

  • 因为 `for` 循环接收数据作为参数,而不是作为输入。 (7认同)