4

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?

2
  • 2
    If this really is the whole script then it lacks the shebang: Add #!/bin/bash as the first line. Also, what exactly do you want read to read? If it's filename then add done <"$filename".
    – dessert
    Nov 23, 2017 at 10:21
  • 3
    I can noting but recommend shellcheck.
    – dessert
    Nov 23, 2017 at 10:23

2 Answers 2

6

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 interpretation 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.

2
  • 1
    Since you're showing a full script with various improvements, I suggest using read -r p--which is one of the things ShellCheck mentions--instead of read p. It's very rare that one actually wants read to interpret backslash escapes, and omitting the -r tends to make human readers (including the author, later) wonder if such an unusual situation is anticipated, so read should virtually always be passed the -r option. In addition, while in this case stripping leading and trailing whitespace is likely intended, you may want to mention the significance of not setting IFS= for read. Nov 23, 2017 at 12:42
  • 3
    Instead of < /dev/null you can also use ssh -n. That's the same as per man. Nov 23, 2017 at 15:41
6

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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .