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.sh
和set -x
进行调试。
【参考方案1】:
问题是do_work.sh
运行ssh
命令,默认情况下ssh
从输入文件的标准输入读取。结果,您只能看到第一行已处理,因为该命令会消耗文件的其余部分并且您的 while 循环终止。
这种情况不仅发生在 ssh
上,还发生在任何读取标准输入的命令上,包括 mplayer
、ffmpeg
、HandBrakeCLI
等。
为防止这种情况发生,请将-n
选项传递给您的ssh
命令,使其从/dev/null
而非标准输入中读取。其他命令有类似的标志,也可以通用< /dev/null
。
【讨论】:
非常有用,帮我运行了这个zsh oneliner:cat hosts |同时读取主机;做 ssh $host do_something ;完成 @rat 你仍然想避免uselesscat
. 你会认为啮齿动物会特别警惕这一点。
while read host ; do $host do_something ; done < /etc/hosts
会避免它。真是救命啊,谢谢!
httpie
是另一个默认读取 STDIN 的命令,并且在 bash 或 fish 循环中调用时会遭受相同的行为。如上所述使用http --ignore-stdin
或将标准输入设置为/dev/null
。【参考方案2】:
一个非常简单且强大的解决方法是更改read
命令接收输入的文件描述符。
这是通过两个修改来实现的:read
的 -u
参数和< $FILENAME
的重定向运算符。
在 BASH 中,默认文件描述符值(即 read
中的 -u
的值)为:
所以只需要选择一些其他未使用的文件描述符,例如 9
只是为了好玩。
因此,解决方法如下:
while read -u 9 LINE; do
let count++
echo "$count $LINE"
sh ./do_work.sh $LINE
done 9< $FILENAME
注意两个修改:
read
变为 read -u 9
< $FILENAME
变为 9< $FILENAME
作为最佳实践,我对我在 BASH 中编写的所有 while
循环执行此操作。
如果您有使用read
的嵌套循环,请为每个循环使用不同的文件描述符 (9,8,7,...)。
【讨论】:
谢谢!这实际上是 IMO 最好的解决方案 非常感谢!这看起来对我来说也是最好的解决方案,因为如果你有更大的脚本,使用这个你不需要找到有问题的命令。 优秀的解决方案!【参考方案3】:这发生在我身上,因为我有 set -e
和 grep
在循环中返回没有输出(这给出了一个非零错误代码)。
【讨论】:
美女,救了我的下午【参考方案4】:更一般地说,不特定于ssh
的解决方法是为任何可能消耗while
循环输入的命令重定向标准输入。
while read -r LINE; do
let count++
echo "$count $LINE"
sh ./do_work.sh "$LINE" </dev/null
done < "$FILENAME"
添加</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
【讨论】:
这没有意义。<<EOF
覆盖 </dev/null
重定向。 done
之后的<<
重定向错误。以上是关于While 循环在 Bash 的第一行之后停止读取的主要内容,如果未能解决你的问题,请参考以下文章
bash: while read loop - 如果有变量 sshpass 则停止
在bash'while read'循环中设置的变量在它之后未设置[重复]