SSF
SSF

Reputation: 983

Two while loops behaving strangely, Bash script

I'm new to Bash scripting. I have written a script to help me get some info using ssh from bunch of servers. the IP address of first set of devices are from 101 to 148, and the other set are from 201 to 210.

#!/bin/bash

BASE=192.168.11
SD_START=101
SD_END=148
HD_START=201
HD_END=210
SD_counter=$SD_START
HD_counter=$HD_START

while [[ $SD_counter -le $SD_END ]]
do 
    ip=$BASE.$SD_counter
    ssh $ip command1
    SD_counter=$(($SD_counter +1))

    if [ "$SD_counter"==148 ]
    then

        while [[ $HD_counter -le $HD_END ]]
        do
            ip=$BASE.$HD_counter

            ssh $ip command2
            HD_counter=$(($HD_counter +1))
        done
    fi 

done > log_SD_HD

echo "Done!"    

But for some reason command1 is executed on 192.168.11.101 first, then command2 is executed on ip range 192.168.11.201-192.168.11.210 which is the second while loop. After that the first while loop continues till the end. Why is this happening? I want the first while loop to be done before the second while loop. Could someone please point out what I'm doing wrong?

Upvotes: 0

Views: 127

Answers (3)

Varvarigos Emmanouil
Varvarigos Emmanouil

Reputation: 757

There is no reason to nest the loops from what you showed:

#!/bin/bash

BASE=192.168.11
SD_START=101
SD_END=148
HD_START=201
HD_END=210
SD_counter=$SD_START
HD_counter=$HD_START

while [[ $SD_counter -le $SD_END ]]
do 
    ip=$BASE.$SD_counter
    ssh $ip command1
    SD_counter=$(($SD_counter +1))
done> log_SD_HD
while [[ $HD_counter -le $HD_END ]]
do
    ip=$BASE.$HD_counter
    ssh $ip command2
    HD_counter=$(($HD_counter +1))
done>> log_SD_HD

echo "Done!" 

Upvotes: 2

mklement0
mklement0

Reputation: 439682

@0x1cf's answer provides the right pointer:

[ "$SD_counter"==148 ] doesn't work as expected.

Specifically: "$SD_counter"==148, based on bash's string synthesizing rules, is expanded to a single string literal: the value of $SD_counter is concatenated with literal ==148, and the resulting string literal is treated as a Boolean. Since a non-empty string in a Boolean context always evaluates to true, [ "$SD_counter"==148 ] always evaluates to true due to lack of spaces around the ==.

Aside from that: in bash you should use [[ ... ]] rather than [ ... ] - it is more robust and provides more features.

Also note (as @0x1cf notes too) that - if using [ ... ] or [[ ... ]] - using the arithmetic operators is the right choice when dealing with numbers: -eq, -ne, -lt, -le, -gt, or -ge.

Generally, though, using (( ... )) expressions - arithmetic evaluation - provides more flexibility with numbers - see below.


That said, your code can be greatly simplified by using arithmetic evaluation - (( ... )) (see section ARITHMETIC EVALUATION in man bash):

It allows you to use C-style arithmetic and Boolean expressions. If we combine this with bash's array variables, your code can be simplified to:

#!/usr/bin/env bash

BASE=192.168.11

START_INDICES=( 101 201 )
END_INDICES=( 148 210 )

COMMANDS=( command1 command2 )

numRanges=${#START_INDICES[@]}

for (( range = 0; range < numRanges; ++range )); do
  cmd=${COMMANDS[range]}
  for (( i=${START_INDICES[range]}; i<=${END_INDICES[range]}; ++i )); do
    ip=$BASE.$i
    ssh $ip $cmd
  done
done > log_SD_HD

Note:

  • (( ... )) expressions DIFFER from normal bash assignments and conditionals in that you:

    • need NOT reference variables with $
    • need NOT double-quote variable references
    • you MAY have spaces around the assignment operator (=)
    • you MAY omit spaces around relational operators: (( SD_counter==148 )) DOES work.
  • ( string1 ... ) creates an array with elements string1, ...; ${#arrayVar[@]} returns the count of elements of array variable arrayVar; ${arrayVar[ndx]} returns the element with (0-based) index ndx.

  • It's better to avoid ALL-UPPERCASE variable names such as BASE, as they may conflict with environment variables, which are by convention typically all-uppercase.

Upvotes: 2

qweruiop
qweruiop

Reputation: 3266

UPDATE

Hint: You can always use #!/bin/bash -x to trace and debug your scripts.


Maybe using two while loop is a good idea, just as V_Maenolis showed. However, to answer your question about what's wrong with your script, try this

Replace

if [ "$SD_counter"==148 ]

with

if [ "$SD_counter" -gt 148 ]

which works for me.

So there are two errors

  1. There should be a space before and after == operator, that is to say, using A == B NOT A==B
  2. The logic of comparing SD_counter == 148 is incorrect. Because when SD_counter hits 148, your script will run into the second while loop, and you'll get 147, 201, ..., 210, 148. Using -gt instead avoids the problem.

Upvotes: 2

Related Questions