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

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

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

所以,我有一个如下所示的列表:

server1
server2
server3
server4

我想我可以使用 while 循环对此进行循环,如下所示:

while read server; do
  ssh $server "uname -a"
done < /home/kenny/list_of_servers.txt

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

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

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

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

while read server; do
  echo $server
done < /home/kenny/list_of_servers.txt

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

答案1

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

set -f
IFS='
'
for x in $(cat file); do …

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

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

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

while read server; do
  ssh -n $server "uname -a"
done < /home/kenny/list_of_servers.txt

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

while read server; do
  ssh $server "uname -a" </dev/null
done < /home/kenny/list_of_servers.txt

避免 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

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

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

答案2

你应该使用while, 不是for。避免命令在这种循环中吞噬标准输入的方法是简单地使用另一个文件描述符:

while read -u 9 server; do
  ssh $server "uname -a"
done 9< /home/kenny/list_of_servers.txt

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

答案3

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

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

答案4

默认的标准输入处理会ssh耗尽 while 循环中的剩余行。

要避免此问题,请更改有问题的命令读取标准输入的位置。如果不需要将标准输入传递给命令,则从特殊/dev/null设备读取标准输入。

相关内容