我有以下 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
从标准输入(即您的输入文件)读取。结果,您只看到处理的第一行,因为该命令消耗了文件的其余部分并且 while 循环终止。
这种情况不仅发生在
ssh
上,还发生在任何读取 stdin 的命令上,包括 mplayer
、ffmpeg
、HandBrakeCLI
、httpie
、brew install
等。
为了防止这种情况,请将
-n
选项传递给 ssh
命令,使其从 /dev/null
而不是标准输入读取。其他命令也有类似的标志,或者您可以普遍使用 < /dev/null
。
一个非常简单且可靠的解决方法是更改 read
命令接收输入的
文件描述符。
这是通过两项修改来完成的:
-u
的read
参数,以及< $FILENAME
的重定向运算符。
在 BASH 中,默认文件描述符值(即
-u
中 read
的值)为:
因此,只需选择其他一些未使用的文件描述符,例如
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
是这里的关键点,尽管更正的引用对于稳健性也有些重要;另请参阅何时在 shell 变量周围加引号?。您将需要使用 read -r
,除非您特别需要在不使用 -r
的情况下在输入中使用反斜杠时获得稍微奇怪的遗留行为。最后,避免私有变量大写。
另一种针对
ssh
的解决方法是确保任何 ssh
命令都有其标准输入,例如通过改变
ssh otherhost some commands here
改为从此处的文档读取命令,这可以方便地(对于此特定场景)将
ssh
的标准输入绑定到命令:
ssh otherhost <<'____HERE'
some commands here
____HERE
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
这发生在我身上,因为我有
set -e
并且循环中的 grep
返回没有输出(这给出了非零错误代码)。
我遇到了类似的问题,但我不是从文件中读取数据,而是从包含一些
csv
数据的 bash 变量中读取数据,因此文件描述符对我不起作用。从
stdin
echo -n | input_stealing_program
示例:
csv_data="Germany,Berlin
France,Paris
Enland,London"
# Set IFS to comma for parsing CSV
IFS=','
while read -r country capital; do
echo -n | input_stealing_programm $country $capital
done <<< "$csv_data"
替代方案是
input_stealing_program < /dev/null