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


36

我正在尝试读取文本文件,并使用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

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

Answers:


25

for循环是在这里很好。但是请注意,这是因为文件包含计算机名称,该计算机名称不包含任何空格字符或globlob字符。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从不读取任何输入,因此仅保留其标准输入。

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

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循环中进行,以便在循环期间将文件打开一次。

通用解决方案是在标准描述符以外的文件描述符上向循环提供输入。外壳具有将输入和输出从一个描述符号传递到另一个描述符号的构造。在这里,我们打开文件描述符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脚本什么时候使用附加的文件描述符?


5
伙计,这个stackexchange肯定不同于serverfault。这里的人们确实竭尽所能,不仅要回答,而且要教育。非常感谢。
肯尼·拉沙特

26

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

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

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


1
有趣的是,我以前从未使用过文件描述符。什么是-u标志吗?我不确定应该在哪个手册页中查找它。
肯尼·拉沙特

read命令是shell bash的一部分,因此它位于bash的手册页的read段落的“ SHELL BUILTIN COMMANDS”部分中。您将找到“ -u fd从文件描述符fd读取输入”。在本段中
f4m8 2011年

6
或者help read- help是用于获取等效的manShell内置页面的命令。
l0b0 2011年

22

在您的第一个代码中,ssh将从中“窃取” STDIN while。添加-n选项ssh以避免它。来自man ssh

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

好的,知道为什么for循环不会发生这种情况吗?
肯尼·拉沙特

7
因为for循环接收数据作为参数,而不是输入。
manatwork 2011年

1
先生,您是一位绅士和学者。
肯尼·拉沙特

0

默认的标准输入处理ssh将while循环中的其余行排空。

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

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.