While loop only processes the first entry of ssh command

Solution 1:

Finally I assumed that, the rest part of the script is okay. Then I followed @dessert's comment and used shellcheck that lead me to the actual issue and its solution:

SC2095: Add < /dev/null to prevent ssh from swallowing stdin.

So you have to change your script in this way:

ssh -p 2222 "$p" 'who -b' < /dev/null | awk '{print $(NF-1)" "$NF}' >> 'file2'

According to the original answer and thanks to the useful advises, provided in the comments by @EliahKagan and @rexkogitans, the complete script could look as this:

#!/bin/bash

# Collect the user's input, and if it`s empty set the default values
[[ -z "${1}" ]] && OUT_FILE="reboot-indication.txt" || OUT_FILE="$1"
[[ -z "${2}" ]] && IN_FILE="hosts.txt" || IN_FILE="$2"

while IFS= read -r host; do
    indication="$(ssh -n "$host" 'LANG=C who -b' | awk '{print $(NF-1)" "$NF}')"
    printf '%-14s %s\n' "$host" "$indication" >> "$OUT_FILE"
done < "$IN_FILE"
  • < /dev/null/ is replaced by the -n option of the ssh command. From man ssh:

    -n   Redirects stdin from /dev/null (actually, prevents reading from stdin). 
         This must be used when ssh is run in the background... This does not work 
         if ssh needs to ask for a password or passphrase; see also the -f option.
    
  • IFS= read -r line - as @StéphaneChazelas says in his encyclopedic answer - is the canonical way to read one line of input with the read builtin.

    • The key is that read reads words from a (possibly backslash-continued) line, where words are $IFS delimited and backslash can be used to escape the delimiters (or continue lines). So the read command should be tweaked to read lines.

    • IFS= changes the internal field separator to the null string, thus we preserve leading and trailing whitespace in the result.

    • The option -r - raw input - disables interpretion of backslash escapes and line-continuation in the read data (reference).

  • printf '%s %s' "$VAR1" "$VAR2" will provide a better output format (reference).

  • LANG=C will guarantee identical output of who -b on each server, thus the parsing of the output with awk will be guaranteed as well.

  • Note here is assumed there is ~/.ssh/config file and additional parameters as -p 2222 are not needed (reference).


Call the above scrip ssh-check.sh (don't forget to chmod +x) and use it in this way:

  • Use the default values of the input (hosts.txt) and the output (reboot-indication.txt) files:

    ./ssh-check.sh
    
  • Set custom value for the output file; set custom value also for the input files:

    ./ssh-check.sh 'my-custom.out'
    ./ssh-check.sh 'my-custom.out' 'my-custom.in'
    

Read this answer to see how the entire approach could be improved.

Solution 2:

You forgot to close the while-do loop. Add done to the end.

filename='file1'
while read p; do
   ssh -p 2222 $p 'who -b' | awk '{print $(NF-1)" "$NF}' >> file2*
done