Ubuntu – While loop only processes the first entry of ssh command

bashssh

So I don't know much about bash and need some pro help.
I am trying to run a script like this one:

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

what I am trying to make is a script that go through all addresses in file1 to see when they last rebooted and then but the answer in file2.

The problem is that it only go through the first address and not the other.

The first address got a password I need to type to proceed the process. May this be the problem or do I specify every line in file1 or am I doing absolutely wrong to begin with?

Best Answer

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.

Related Question