Shell interesting shell conundrum

SirDice

Administrator
Staff member
Administrator
Moderator
I've done this a million times in the past, never had an issue with it. Now all of a sudden it doesn't work anymore. Originally had this issue on RHEL but I can actually reproduce it on FreeBSD too. We figured out a way around this but we fail to understand why it's happening in the first place. Hoping some shell script guru here might be able to shine a light on it.

We have a simple file with a list of hosts (in reality this is a CSV but the CSV is not the issue):
hosts.txt
Code:
host1
host2
host3

Now, on a bourne shell I've done this countless times as a quick and dirty way of connecting to a bunch of hosts to get some info:
Code:
$ cat hosts.txt | while read h; do
> echo -n "$h: "
> ssh $h "uname -a"
> done

I expect this to loop over the hosts in the file and run ssh(1), log in on the other computer and run the command there. It works but only does the first host and the script ends. It never does the rest of the hosts in the file.

If I do this:
Code:
$ cat hosts.txt | while read h; do
> echo -n "$h: "
> ssh -n $h "uname -a"
> done
Or replace the ssh line with an echo for example:
Code:
$ cat hosts.txt | while read h; do
> echo -n "$h: "
> echo ssh $h "uname -a"
> done

It does loop over all the hosts in the file. Why doesn't this work (anymore) with ssh(1)?

This also works as expected:
Code:
$ for h in $(cat hosts.txt); do
> echo -n "$h: "
> ssh $h "uname -a"
> done

If you put this in a script file you get the same issues, so it's not restricted to the command line, command line just makes it a little easier to test.

Why? Why does the while ..;do only executes the first host and apparently skips everything else. Why does ssh(1) all of a sudden require the -n here? And why does a for .. ; do work when a while ... ; do doesn't?
 
ssh drains the input
Code:
$ cat aaa
buko
never mind the bollocks
buko
$ while read  i;do echo -n $i:; truss ssh $i "uname -a" 2>&1|grep bollocks;cat ;done <aaa
buko:read(5,"never mind the bollocks\nbuko\n",16384)     = 29 (0x1d)
$
 
Yeah, I've found some other explanations that say the same. But this never was the case in the past, as I've done this countless times without any issue. In any case, it's a nice pitfall that will get you unexpected results.

I could probably circumvent this in another way by using echo "uname -a" | ssh $h
 
You can use ssh $h uname -a </dev/null. I never use the idiom you use with anything that can read it’s input so I’m curious as to which version of sh works the way you expected.
 
You can use ssh $h uname -a </dev/null
I prefer the -n option in that case.

I'm wondering if the default of StdinNull changed at some point. That would explain the differences in behavior and why it worked in the past without having to set it.
 
the input is drained after the remote command is executed so if the remote command needs to read from stdin it works
cat -n /etc/hosts |while true;do ssh buko "head -5";cat ;read junk||break ;done
 
Back
Top