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

While loop stops reading after the first line in Bash

提问人:bcbishop 提问时间:12/10/2012 最后编辑:oguz ismailbcbishop 更新时间:12/28/2022 访问量:91961

问:

我有以下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.shssh

bash shell ssh while-loop

评论

1赞 sleepsort 12/10/2012
您的脚本很好,但do_work.sh可能有问题
2赞 Michael Krelin - hacker 12/10/2012
是的,它可以吃掉所有输入,或者可以调用它作为并简单地退出或 .但是这段代码看起来不真实,OP 会注意到 echo 需要正确显示换行符......sourceexec-e
3赞 dogbane 12/10/2012
有没有机会跑?do_work.shssh
1赞 bcbishop 12/10/2012
是的,do_work.sh运行几个 ssh 命令。这有什么特别之处吗?
1赞 koola 12/10/2012
最好显示源代码并运行以进行调试。do_work.shdo.shset -x

答:

276赞 dogbane 12/10/2012 #1

问题是运行命令,默认情况下从 stdin(您的输入文件)读取。因此,您只能看到已处理的第一行,因为该命令会占用文件的其余部分,并且 while 循环会终止。do_work.shsshssh

这不仅发生在 上,还发生在任何读取 stdin 的命令上,包括 、 、 、 、 等。sshmplayerffmpegHandBrakeCLIhttpiebrew install

为防止出现这种情况,请将该选项传递给命令,使其读取自 而不是 stdin。其他命令具有类似的标志,或者您可以普遍使用 .-nssh/dev/null< /dev/null

评论

2赞 rat 1/3/2018
非常有用,帮助我运行了这个 zsh oneliner: cat hosts |while 读取主机 ;做 ssh $host do_something ;做
6赞 tripleee 7/19/2019
@rat 你还是想避开那只没用的你会认为啮齿动物会特别警惕这一点。
0赞 rat 7/19/2019
while read host ; do $host do_something ; done < /etc/hosts会避免它。这真是救命稻草,谢谢!
0赞 Raman 11/16/2019
httpie是另一个默认读取 STDIN 的命令,在 bash 或 fish 循环中调用时将遭受相同的行为。使用或设置标准输入如上所述。http --ignore-stdin/dev/null
0赞 JackTheKnife 8/16/2023
案件的解决方案是什么?gcloud compute ssh
5赞 jacobm654321 11/15/2015 #2

ssh -n 选项可防止在将输出通过管道传输到另一个程序时使用 HEREdoc 时检查 ssh 的退出状态。 因此,首选使用 /dev/null 作为 stdin。

#!/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

评论

1赞 tripleee 3/25/2019
这没有意义。覆盖重定向。之后的重定向是错误的。<<EOF</dev/null<<done
4赞 JonnyRaa 1/31/2018 #3

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

评论

2赞 cachonfinga 4/22/2021
你这个美女,刚刚救了我的午饭
0赞 tripleee 11/24/2023
也许可以查看 stackoverflow.com/questions/19622198/...,了解更多详情set -e
34赞 tripleee 3/25/2019 #4

更一般地说,一种不特定于的解决方法是重定向任何命令的标准输入,否则这些命令可能会消耗循环的输入。sshwhile

while read -r line; do
   ((count++))
   echo "$count $line"
   sh ./do_work.sh "$line" </dev/null
done < "$filename"

添加 是这里的关键点,尽管更正后的引用对于稳健性也很重要;另请参阅何时将引号括在 shell 变量周围?。除非您特别要求在输入中为不带 .最后,避免私有变量使用大写字母。</dev/nullread -r-r

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

ssh otherhost some commands here

而是从 here 文档中读取命令,该文档方便地(对于此特定方案)绑定了 for the commands 的标准输入:ssh

ssh otherhost <<'____HERE'
    some commands here
____HERE
58赞 cmo 2/13/2021 #5

一个非常简单和可靠的解决方法是更改命令从中接收输入的文件描述符read

这是通过两个修改来实现的:对 的参数和 的重定向运算符。-uread< $FILENAME

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

  • 0 = 标准
  • 1 = 标准输出
  • 2 = stderr

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

因此,以下是解决方法:

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

请注意以下两个修改:

  1. read成为read -u 9
  2. < $FILENAME成为9< $FILENAME

作为最佳实践,我对我在 BASH 中编写的所有循环都这样做。 如果您有嵌套循环 using ,则对每个循环使用不同的文件描述符 (9,8,7,...)。whileread

评论

10赞 Iman Akbari 2/25/2021
谢谢!这实际上是IMO这里最好的解决方案
3赞 ced-mos 7/8/2021
谢谢!这对我来说看起来也是最好的解决方案,因为如果您有更大的脚本,则无需找到有问题的命令。
1赞 Feriman 12/16/2021
优秀的解决方案!
1赞 ambikanair 4/8/2022
这!!!这个工作!!
1赞 Abhijay Ghildyal 5/22/2022
谢谢!这是唯一对我有用的解决方案。
1赞 user1556435 11/8/2022
这是一个非常好的技巧。令人惊讶的是,任意命令最终都会读取 stdin。就我而言,它是.这是通用解决方案,不需要读取特定于命令的手册页(如果有的话)。sloccount
1赞 Tim Bird 1/19/2023
恕我直言,这是一个比公认的解决方案更好的解决方案。除了 ssh 之外,还有其他命令读取 stdin,弄乱了 while 循环。这个解决方案,使用不同的文件描述符,允许我在循环中保持现有的被调用基础结构相同。这使我不必测试与 sshpass 或循环中可能影响 stdin 的其他程序的交互。谢谢!!
0赞 Red_badger 3/10/2023
谢谢!这对我有用,保存修改了我拥有的许多 ssh 命令。虽然我尝试了 Dogbane 的首选解决方案,但在我的情况下添加 -n 和 </dev/null 也有效,但我更喜欢这个,因为它只是改变了 while 循环。一个非常整洁的解决方案,我将尝试将其提交到内存中!