0

I store in a file named ssh_hosts.txt a list of hostnames. In a shell script, I loop over the hostnames in ssh_hosts.txt and execute a command on the named host via SSH. The shell script is shown below.

The problem is that the script exits after processing the first host. But if I do something other than execute a command on the named host via ssh, the script will run to completion.

In the example below, I've commented out the call to ssh and replaced it with a simple echo of the current host name. This will run to completion.

I am executing this script from a bash shell running under the following Cygwin version on Windows 7:

$ uname -a
CYGWIN_NT-6.1 myHostname 1.7.16(0.262/5/3) 2012-07-20 22:55 i686 Cygwin

These SSH versions are involved:

$ ssh -V
OpenSSH_6.0p1, OpenSSL 1.0.1c 10 May 2012

$ ssh myUsername@remoteHost 'ssh -V'
myUsername@remoteHost password:
OpenSSH_6.7p1 Debian-5+deb8u3, OpenSSL 1.0.1t  3 May 2016

Here is the shell script:

#!/bin/bash

if [[ $# -ne 1 ]]; then
   echo "Usage: $(basename $0) <user name>"
   exit 1
fi

USER="$1"

while IFS='' read -r ssh_host || [[ -n "$ssh_host" ]];
do
   # This line will execute for all hosts listed in ssh_hosts.txt.
   echo $ssh_host

   # This line will execute for *only the first* host in ssh_hosts.txt.
   # ssh $USER@$ssh_host 'echo $(whoami)@$(hostname)'
done < ssh_hosts.txt

How may I get this shell script to execute over all hosts in ssh_hosts.txt rather than just over the first host?

2

I think you are making it too complicated:

for ssh_host in $(cat ssh_hosts.txt)
do
   echo $ssh_host
   ssh $user@$ssh_host ....
done

Also, search github for gsh, which is a perl program that will do more if you do this often.

  • I would have loved to have selected all of the offered answers as "the" answer--they were all very good and I sure appreciate people's time in offering them--, but ultimately I picked this one because it most simply and directly solved my specific problem. Thank you to dave_thompson_085, virtex, dgoo2308, and eggo! – Dave Jun 16 '17 at 11:19
3

You have two programs in your loop both reading from stdin: read and ssh. When your loop runs, the read command will read the first line from ssh_hosts.txt, then ssh slurps up the rest. That's why you only see it connect to the first host.

I know of two solutions to this problem. One is to rewrite your loop so it doesn't redirect from stdin. A good way to do this is with the for loop that eggo has in his post.

The other solution is to pass the -n option to ssh:

ssh -n $USER@$ssh_host 'echo $(whoami)@$(hostname)'

This tells ssh not to use stdin, leaving the full contents of your ssh_hosts.txt to the read command. This solution requires your ssh sessions to not require any interaction with the user, including prompting for a password (so use ssh keys for authentication).

  • Ahhh stdin, thank you for explaining the underlying problem! – Dave Jun 16 '17 at 11:14
2

You need to fork it as the ssh will mess up your loop.

I got a script on hand for updating my authorised keys, I did this with 2 scripts:

  • run.sh: read your host list and fork the do_command.sh script
  • do_command.sh: make connection with control socket, execute commands and close

As an extra bonus:

  • all hosts are connected simultaneous
  • multiple commands are executed with one connection
  • a log file with the results

Main script: run.sh

#!/bin/bash
TIMEOUT=60
COUNT=0
rm -rf log
mkdir log
while read -u10 HOST ; do ( ./do_command.sh $HOST & ); done 10< ssh_hosts.txt
sleep 1
for f in log/*.pid; do
    [ -e "$f" ] && VMN=`ls log/*.pid`
    echo "waiting for process to finish"
    break
done
while [ "$VMN" != "" ]
do

  sleep 3
  COUNT=$(($COUNT+3))
  if (("$COUNT" != "$TIMEOUT"))
    then
        VMN=""
        for f in log/*.pid; do
            [ -e "$f" ] && VMN=`ls log/*.pid`
            break
        done
        if [ "$VMN" != "" ]
        then
            echo "waiting already "$COUNT" seconds ..."
            #for VM in $VMN; do 
                #myPID=$(<"$VM")
                #echo -e "\t pid:$myPID \t $VM "

                #done
        fi
    else
        echo -e "\n!!! timeout, waiting for remaining processes's to shutdown"
        for VM in $VMN; do
            myPID=$(<"$VM")
            ITEM=$(sed -e 's/.pid//;s/log\///' <<<"$VM")
            LOGX="log/$ITEM.log"
            echo -e "killing $myPID -> item: $ITEM \t log: $LOGX" 
            kill -9 $myPID 
            rm -rf $VM
            echo -e "\n ###### main Process ###### \n\n \t Killed due to Time out \nFAILED $ITEM" >>$LOGX
            echo "$ITEM Killed due to Time out" >>log/error.txt
        done
        rm -fv log/*.sock
        VMN=''
    fi
done
if [ -f log/error.txt ] 
then
    rm -fv log/*.sock
    echo -e "\n##### FAIL ##############\n"
    cat log/error.txt
    echo
    exit 1
fi
echo

Action script: do_command.sh

#!/bin/bash
exec 5<&1 
exec 6<&2
echo "PROCESS $1"
myPID=$$
echo $$ >"log/$1.pid"
chmod 777 "log/$1.pid"
exec 1> "log/$1.log" 2>&1
echo "PROCESS $1"
SOCKET="log/$1_$myPID.sock"
TARGET="root@$1"
echo "Connecting to $TARGET .... "
ssh -f -N -M -o ControlPath=$SOCKET $TARGET
if [ $? -ne 0 ]; then
    echo "Could not connect $1, Cleanup "
    echo "Could not connect $1, exit " >>log/error.txt
    #ssh -S $SOCKET -O exit $TARGET
    rm -fv "log/$1.pid"
    rm -fv $SOCKET
    echo "FAILED $TARGET">&6
    echo "FAILED $TARGET"
    exit 1
fi

# Do the thing you need to do
# This script copy a file to the remote and executes 2 commands
#
scp -o ControlPath=$SOCKET authorized_keys $TARGET:.ssh/new_authorized_keys

# Execute a command:
ssh -o ControlPath=$SOCKET $TARGET "mv -fv .ssh/authorized_keys .ssh/old_authorized_keys && cp -fv .ssh/new_authorized_keys .ssh/authorized_keys && chmod 600 .ssh/authorized_keys"

# Another remote command:   
ssh -o ControlPath=$SOCKET $TARGET "ls -la .ssh"

# And Close the socket 
ssh -S $SOCKET -O exit $TARGET

sleep 1
echo Cleanup
rm -fv "log/$1.pid"
rm -fv $SOCKET
echo "SUCCESS $TARGET"
echo "SUCCESS $TARGET">&5
exit 0
  • Very nice solution, and yes, the problem I'm trying to solve is that of creative an effective SSH key (both host and user keys) management solution. – Dave Jun 16 '17 at 11:15
  • @Dave don't forget to mark as answered – Danny Goossen Jun 22 '17 at 8:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.