Avoiding input problems with ssh

Another vexing problem with reading lines in a while read -r loop is that sometimes certain commands inside the loop can themselves consume some of the data intended for the read builtin, and it may not be immediately clear what those commands are.

One such command is the ssh OpenSSH client, which by default reads all the standard input it can once started to pass it as commands to the target system, even if a command is specified on the command line. Consider this list of SSH hostnames:

$ cat hostnames
alpha.example.com.
beta.example.com.
gamma.example.com.

We want to read each hostname, and run the uptime command on it to get a quick overview of how long it's been up and its load average. However, this doesn't seem to work:

while read -r hostname ; do
    ssh -- "$hostname" uptime
done < hostnames

The first iteration of the loop runs fine, and after perhaps typing a password or passphrase, we see the uptime of alpha.example.com. – but then the loop stops, with no error message!

This is because ssh has read the remaining two lines of the hostnames file during the first loop iteration, after the first line was saved into the hostname variable by the read builtin. The -n switch to ssh suppresses this behavior, instructing it not to read any standard input:

while read -r hostname ; do
    ssh -n -- "$hostname" uptime
done < hostnames

The loop then runs three times, reading each host and printing the output of its uptime program after running it via SSH.

Any program that reads standard input without you expecting it to may potentially run into this problem. If you can't find a way to prevent the program from reading standard input with configuration or options, you can try directing /dev/null into it:

while read -r item ; do
    program </dev/null "$item"
done < items