While 循环在 Bash 的第一行之后停止读取

Posted

技术标签:

【中文标题】While 循环在 Bash 的第一行之后停止读取【英文标题】:While loop stops reading after the first line in Bash 【发布时间】:2012-11-27 19:45:55 【问题描述】:

我有以下 shell 脚本。目的是循环遍历目标文件的每一行(其路径是脚本的输入参数)并对每一行进行处理。现在,它似乎只适用于目标文件中的第一行,并在该行被处理后停止。我的脚本有什么问题吗?

#!/bin/bash
# SCRIPT: do.sh
# PURPOSE: loop thru the targets 

FILENAME=$1
count=0

echo "proceed with $FILENAME"

while read LINE; do
   let count++
   echo "$count $LINE"
   sh ./do_work.sh $LINE
done < $FILENAME

echo "\ntotal $count targets"

do_work.sh 中,我运行了几个ssh 命令。

【问题讨论】:

您的脚本没问题,但 do_work.sh 可能有问题 是的,它可以吃掉所有输入,或者它可以被调用为source 并简单地退出或exec。但是这段代码看起来并不真实,OP 会注意到 echo 需要 -e 才能正确显示换行... do_work.sh 是否会运行 ssh 是的,do_work.sh 运行几个 ssh 命令。有什么特别的吗? 最好显示do_work.sh 源代码并运行do.shset -x 进行调试。 【参考方案1】:

问题是do_work.sh 运行ssh 命令,默认情况下ssh 从输入文件的标准输入读取。结果,您只能看到第一行已处理,因为该命令会消耗文件的其余部分并且您的 while 循环终止。

这种情况不仅发生在 ssh 上,还发生在任何读取标准输入的命令上,包括 mplayerffmpegHandBrakeCLI 等。

为防止这种情况发生,请将-n 选项传递给您的ssh 命令,使其从/dev/null 而非标准输入中读取。其他命令有类似的标志,也可以通用&lt; /dev/null

【讨论】:

非常有用,帮我运行了这个zsh oneliner:cat hosts |同时读取主机;做 ssh $host do_something ;完成 @rat 你仍然想避免useless cat. 你会认为啮齿动物会特别警惕这一点。 while read host ; do $host do_something ; done &lt; /etc/hosts 会避免它。真是救命啊,谢谢! httpie 是另一个默认读取 STDIN 的命令,并且在 bash 或 fish 循环中调用时会遭受相同的行为。如上所述使用http --ignore-stdin 或将标准输入设置为/dev/null【参考方案2】:

一个非常简单且强大的解决方法是更改​​read 命令接收输入的文件描述符

这是通过两个修改来实现的:read-u 参数和&lt; $FILENAME 的重定向运算符。

在 BASH 中,默认文件描述符值(即 read 中的 -u 的值)为:

0 = 标准输入 1 = 标准输出 2 = 标准错误

所以只需要选择一些其他未使用的文件描述符,例如 9 只是为了好玩。

因此,解决方法如下:

while read -u 9 LINE; do
   let count++
   echo "$count $LINE"
   sh ./do_work.sh $LINE
done 9< $FILENAME

注意两个修改:

    read 变为 read -u 9 &lt; $FILENAME 变为 9&lt; $FILENAME

作为最佳实践,我对我在 BASH 中编写的所有 while 循环执行此操作。 如果您有使用read 的嵌套循环,请为每个循环使用不同的文件描述符 (9,8,7,...)。

【讨论】:

谢谢!这实际上是 IMO 最好的解决方案 非常感谢!这看起来对我来说也是最好的解决方案,因为如果你有更大的脚本,使用这个你不需要找到有问题的命令。 优秀的解决方案!【参考方案3】:

这发生在我身上,因为我有 set -egrep 在循环中返回没有输出(这给出了一个非零错误代码)。

【讨论】:

美女,救了我的下午【参考方案4】:

更一般地说,不特定于ssh 的解决方法是为任何可能消耗while 循环输入的命令重定向标准输入。

while read -r LINE; do
   let count++
   echo "$count $LINE"
   sh ./do_work.sh "$LINE" </dev/null
done < "$FILENAME"

添加&lt;/dev/null 是这里的关键点(尽管更正的引用也有些重要;另请参阅When to wrap quotes around a shell variable?)。您将希望使用 read -r,除非您特别需要在没有 -r 的情况下获得的旧式稍微奇怪的行为。

另一种特定于ssh 的解决方法是确保任何ssh 命令都绑定了其标准输入,例如通过改变

ssh otherhost some commands here

改为从此处的文档中读取命令,该文档方便地(对于此特定场景)为命令绑定ssh 的标准输入:

ssh otherhost <<'____HERE'
    some commands here
____HERE

【讨论】:

【参考方案5】:

ssh -n 选项可防止在使用 HEREdoc 将输出传输到另一个程序时检查 ssh 的退出状态。 所以最好使用 /dev/null 作为标准输入。

#!/bin/bash
while read ONELINE ; do
   ssh ubuntu@host_xyz </dev/null <<EOF 2>&1 | filter_pgm 
   echo "Hi, $ONELINE. You come here often?"
   process_response_pgm 
EOF
   if [ $PIPESTATUS[0] -ne 0 ] ; then
      echo "aborting loop"
      exit $PIPESTATUS[0]
   fi
done << input_list.txt

【讨论】:

这没有意义。 &lt;&lt;EOF 覆盖 &lt;/dev/null 重定向。 done 之后的&lt;&lt; 重定向错误。

以上是关于While 循环在 Bash 的第一行之后停止读取的主要内容,如果未能解决你的问题,请参考以下文章

bash: while read loop - 如果有变量 sshpass 则停止

带有条件的 Bash 单行 while 循环的语法

在bash'while read'循环中设置的变量在它之后未设置[重复]

Bash脚本,while循环中的多个条件

java I/O 读取一个txt文件中每一个空行之后的第一行,怎么办?

在使用读取(bash)的while循环中禁用用户输入