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

问题描述 投票:0回答:6

我有以下 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
命令。

bash shell ssh while-loop
6个回答
276
投票

问题是

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

这种情况不仅发生在

ssh
上,还发生在任何读取 stdin 的命令上,包括
mplayer
ffmpeg
HandBrakeCLI
httpie
brew install
等。

为了防止这种情况,请将

-n
选项传递给
ssh
命令,使其从
/dev/null
而不是标准输入读取。其他命令也有类似的标志,或者您可以普遍使用
< /dev/null


58
投票

一个非常简单且可靠的解决方法是更改 read 命令接收输入的

文件描述符

这是通过两项修改来完成的:

-u
read
参数,以及
< $FILENAME
的重定向运算符。

在 BASH 中,默认文件描述符值(即

-u
read
的值)为:

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

因此,只需选择其他一些未使用的文件描述符,例如

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 中编写的所有

while
循环执行此操作。 如果您使用
read
进行嵌套循环,请为每个循环使用不同的 文件描述符(9,8,7,...)。


34
投票

更一般而言,并非特定于

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

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

4
投票

这发生在我身上,因为我有

set -e
并且循环中的
grep
返回没有输出(这给出了非零错误代码)。


0
投票

我遇到了类似的问题,但我不是从文件中读取数据,而是从包含一些

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

© www.soinside.com 2019 - 2024. All rights reserved.