While loop stops reading after the first line in Bash
A very simple and robust workaround is to change the file descriptor from which the read
command receives input.
This is accomplished by two modifications: the -u
argument to read
, and the redirection operator for < $FILENAME
.
In BASH, the default file descriptor values (i.e. values for -u
in read
) are:
- 0 = stdin
- 1 = stdout
- 2 = stderr
So just choose some other unused file descriptor, like 9
just for fun.
Thus, the following would be the workaround:
while read -u 9 LINE; do
let count++
echo "$count $LINE"
sh ./do_work.sh $LINE
done 9< $FILENAME
Notice the two modifications:
read
becomesread -u 9
< $FILENAME
becomes9< $FILENAME
As a best practice, I do this for all while
loops I write in BASH.
If you have nested loops using read
, use a different file descriptor for each one (9,8,7,...).
The problem is that do_work.sh
runs ssh
commands and by default ssh
reads from stdin which is your input file. As a result, you only see the first line processed, because the command consumes the rest of the file and your while loop terminates.
This happens not just for ssh
, but for any command that reads stdin, including mplayer
, ffmpeg
, HandBrakeCLI
, httpie
, brew install
, and more.
To prevent this, pass the -n
option to your ssh
command to make it read from /dev/null
instead of stdin. Other commands have similar flags, or you can universally use < /dev/null
.
More generally, a workaround which isn't specific to ssh
is to redirect standard input for any command which might otherwise consume the while
loop's input.
while read -r LINE; do
let count++
echo "$count $LINE"
sh ./do_work.sh "$LINE" </dev/null
done < "$FILENAME"
The addition of </dev/null
is the crucial point here (though the corrected quoting is also somewhat important; see also When to wrap quotes around a shell variable?). You will want to use read -r
unless you specifically require the legacy slightly odd behavior you get without -r
.
Another workaround of sorts which is somewhat specific to ssh
is to make sure any ssh
command has its standard input tied up, e.g. by changing
ssh otherhost some commands here
to instead read the commands from a here document, which conveniently (for this particular scenario) ties up the standard input of ssh
for the commands:
ssh otherhost <<'____HERE'
some commands here
____HERE