9

I have this script:

#!/bin/sh
SERVERLIST=hosts
ICMD='cat /etc/redhat-release'
while read SERVERNAME
do
   ssh $SERVERNAME $ICMD
done < "$SERVERLIST"

With hosts just like:

hostname
hostname
hostname

When I run the script ./run.sh it only goes to a single host and stops. Why is it not reading the entire host list?

Kahn
  • 1,652
  • 2
  • 19
  • 36
  • 1
    See [BashFAQ #89: I'm reading a file line by line and running ssh or ffmpeg, only the first line gets processed!](http://mywiki.wooledge.org/BashFAQ/089) – Gordon Davisson Jan 21 '21 at 18:29

2 Answers2

18

ssh pre-reads stdin on the assumption that the remote job will want some data, and it makes for a faster start-up. So the first job eats the rest of your hosts list.

You can prevent this happening either using the ssh -n option, or by redirecting like ssh < /dev/null. Being a belt-and-braces man, I do both: ssh -n < /dev/null <other ssh options>.

If you really want the remote job to read something from the local system, you need to pipe it, or here-doc it, for each iteration. Even with for <host>, ssh will read an indeterminate amount of data from stdin for each host start-up.

I would always ping a remote host to get status before ssh to it. IIRC, ping -W has a hard timeout option, while ssh can hang in DNS or while connecting for 90+ seconds, for an unknown or dead host.

Paul_Pedant
  • 8,228
  • 2
  • 18
  • 26
  • 1
    That makes sense! It explains why the [original answer](https://unix.stackexchange.com/a/80204/8965) OP referenced (in my since-deleted incorrect assumption) works but this one doesn't. – Aaron D. Marasco Jan 20 '21 at 15:24
  • @AaronD.Marasco The second answer in that thread has the same fault anyway -- I was not certain which was being cited. – Paul_Pedant Jan 20 '21 at 15:38
  • Another trick is to use a separate file-descriptor in the `3 – D. Ben Knoble Jan 21 '21 at 00:05
7

You could also use pdsh for this, it allows running a command in parallel on multiple hosts.

pdsh -w ^hostlist.txt -R ssh "cat /etc/redhat-release"

^ defines a file with the list of hostnames, alternatively a comma separated list could be used:

pdsh -w host1,host2,host3,... -R ssh "cat /etc/redhat-release"

A result would look like:

pdsh, -w raspi,raspi.local,192.168.0.3,localhost,127.0.0.1 -R ssh "cat /etc/issue"

raspi: Raspbian GNU/Linux 10 \n \l
raspi: 
raspi.local: Raspbian GNU/Linux 10 \n \l
raspi.local: 
192.168.0.3: Raspbian GNU/Linux 10 \n \l
192.168.0.3: 
localhost: Debian GNU/Linux 10 \n \l
localhost: 
127.0.0.1: Debian GNU/Linux 10 \n \l
127.0.0.1: 
FelixJN
  • 12,616
  • 2
  • 27
  • 48