>我有以下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
运行ssh
命令,默认情况下ssh
从输入文件的 stdin 读取。因此,您只能看到已处理的第一行,因为该命令会占用文件的其余部分,并且您的 while 循环会终止。
这不仅发生在ssh
,也发生在任何读取 stdin(包括 mplayer
、ffmpeg
、HandBrakeCLI
、httpie
、brew install
等)上。
若要防止出现这种情况,请将 -n
选项传递给 ssh
命令,使其从/dev/null
而不是 stdin 读取。其他命令具有类似的标志,或者您可以普遍使用 < /dev/null
。
一个非常简单而强大的解决方法是更改 read
命令从中接收输入的文件描述符。
这是通过两个修改来实现的:read
的-u
参数和< $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
-
< $FILENAME
变得9< $FILENAME
作为最佳实践,我对我在 BASH 中编写的所有循环while
执行此操作。如果您有使用 read
的嵌套循环,则对每个循环使用不同的文件描述符 (9,8,7,...)。
更一般地说,一种不特定于ssh
的解决方法是重定向任何命令的标准输入,否则这些命令可能会消耗while
循环的输入。
while read -r line; do
((count++))
echo "$count $line"
sh ./do_work.sh "$line" </dev/null
done < "$filename"
添加</dev/null
是这里的关键点,尽管更正后的引用对于健壮性也有些重要;另请参阅何时将引号括在外壳变量周围?。您将需要使用read -r
,除非您特别要求输入中的反斜杠(没有-r
)的稍微奇怪的遗留行为。最后,避免使用私有变量的大写。
另一种特定于ssh
的解决方法是确保任何ssh
命令都绑定其标准输入,例如通过更改
ssh otherhost some commands here
改为从 here 文档中读取命令,该文档方便地(对于此特定场景)绑定了命令的标准输入 ssh
:
ssh otherhost <<'____HERE'
some commands here
____HERE
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
这发生在我身上,因为我有set -e
并且循环中的grep
返回没有输出(给出非零错误代码)。