3

Background

I have created a script to:

  • Read a list of ip's one at a time
  • Copy a config file from local host to remote host
  • Reboot remote host
  • Close current ssh session

Contents of script:

#!/bin/bash

SSHPASS="OMITTED"
FILE_TO_COPY=/opt/someConfigFile.conf
TARGET_FOLDER=/opt/

echo "Reading ip list..."
cat $1 | while read ip
do
    echo "Copying file to $ip"
    sshpass -p $SSHPASS scp -o StrictHostKeyChecking=no -o ServerAliveInterval=3 $FILE_TO_COPY root@$ip:$TARGET_FOLDER
    echo "Sending reboot command to $ip"
    sshpass -p $SSHPASS ssh -o StrictHostKeyChecking=no -o ServerAliveInterval=3 'nohup reboot > /dev/null & exit'
    echo "Done for $ip"
done
echo "Done for all"

My script read entries from a text file where each entry are separated by a new line, something like:

192.168.XXX.XX1
192.168.XXX.XX2
192.168.XXX.XX3
192.168.XXX.XX4

When I run this script, ./ConfigSender.sh /host_list.txt I can see the following ouput:

$> ./ConfigSender.sh /host_list.txt
Rading ip list...
Copying file to 192.168.XXX.XX1
Sending reboot command to 192.168.XXX.XX1
Done for 192.168.XXX.XX1
Done for all
$>

I expected to see the output for all entries in file. I suspected that commands inside while somehow broke the execution. So I edited my script to just prints the read ip value to output:

#...
echo "Reading ip list..."
cat $1 | while read ip
do
    echo "Copying file to $ip"
done
echo "Done for all"

This is the output:

$> ./ConfigSender.sh /host_list.txt
Rading ip list...
Copying file to 192.168.XXX.XX1
Copying file to 192.168.XXX.XX2
Copying file to 192.168.XXX.XX3
Copying file to 192.168.XXX.XX4
Done for all
$>

Question

It is clear that commands inside original while cause this behavior. What is wrong with while loop? What am I missing here?

EDIT

Both @harrymc & @Kamil Maciorowski answers are solving my issue, many thanks. I decided to accept @harrymc's answer as it is more descriptive.

3

sshpass works by manipulating stdin to fool ssh into thinking it is getting the password from an interactive user. When you use a ... | while style loop, the loop iterates for every line coming from stdin, which sshpass wipes out after the first call. That's why only the first line gets executed.

Several solutions are possible :

  • Redirect for sshpass the standard input to /dev/null
  • Wrap the whole loop body into braces to isolate stdin ({})
  • Assign to an array before the loop so that you are not using stdin
    readarray a < file
    for ip in "${a[@]}"; do
  • Loop over a file descriptor other than stdin
    while read -u 5 -r ip; do
    ...
    done 5<file
2

I made some tests. I think your lines like

sshpass …

read stdin, they "eat" additional IPs provided by cat. You can investigate why it happens but it's enough to know that it does happen.

The solution is to give them some other input:

</dev/null sshpass …

Or you can rebuild your loop like this:

for ip in `cat $1`
do
 …
done
0

Not sure it will solve the problem, but you do not have provided the user and hostname in your ssh reboot command line.

sshpass -p $SSHPASS ssh -o StrictHostKeyChecking=no -o ServerAliveInterval=3 root@$ip 'nohup reboot > /dev/null & exit'

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.