Bash – Problems iterating over several Bash arrays in one loop

arraybashshell-script

I have problems with some arrays in bash.

A=( "127.0.0.1" "localhost" "aaa nnn cvcc" )
B=( "8.8.8.8"  "dns" "bbb tttt rrrr")

for n in ${A} ${B} ;  do

 if ping -c3 ${n[0]};then
  echo "${n[1]}"
    for share in ${n[2]};do
      echo $share
    done
 fi
done

I'd like to print the second and third element of the array but the for loop stop at the ping.
This way it is working.

if ping -c3 ${A[0]};then
 echo "${A[1]}"
 for share in ${A[2]};do
  echo $share
 done
fi

I'm sure must be a very silly thing but it is driving me mad…
Some ideas?
Many thanks in advance

Best Answer

Your loop actually loops over the first elements of A and B, i.e. ${A[0]} and ${B[0]}, which later means that ${n[1]} and ${n[2]} would be empty strings while ${n[0]} is the same as $n.

I would probably employ a name reference variable to do this in bash (release 4.3 or later):

#!/bin/bash

A=( 127.0.0.1 localhost alpha beta gamma theta )
B=( 8.8.8.8   dns       itsy bitsy 'spider man' )

for variable in A B; do
        declare -n array="$variable"

        if ping -c3 "${array[0]}" >/dev/null; then
                printf '%s\n' "${array[1]}"
                printf '\t%s\n' "${array[@]:2}"
        fi
done

I'm looping over the variable names A and B, not their contents. In the loop, I create array as a name reference to the variable named by $variable. Now array is used exactly as either A or B.

In the if statement, I don't rely on the shell to split the third array element for looping. Instead I split it manually when I create the A and B arrays and I just output those elements with a single call to printf using an offset into the array array. I output a tab in front of each element for readability.

localhost
        alpha
        beta
        gamma
        theta
dns
        itsy
        bitsy
        spider man

The if statement could also be written as

if ping -c3 "${array[0]}" >/dev/null; then
        printf '%s: ' "${array[1]}"
        ( IFS=,; printf '%s\n' "${array[*]:2}" )
fi

to get the rest of the arrays elements outputted as a comma-delimited list after a colon:

localhost: alpha,beta,gamma,theta
dns: itsy,bitsy,spider man

Note: None of the quoting is accidental above. Quoting the expansion of an array ensures that each element is individually quoted. Quoting the expansion of a single element ensures that the shell is not splitting the value on whitespaces (and doesn't do filename globbing on the split-up words).

See also


You could also do this with /bin/sh without named arrays. Instead we use the list of positional parameters to hold our data, and terminate each section of that list with a special word (we use END, this is an arbitrary string that we choose). We then loop over that list and shift elements off it as we go:

#!/bin/sh

set --  127.0.0.1 localhost alpha beta gamma theta END \
        8.8.8.8   dns       itsy bitsy 'spider man' END

while [ "$#" -gt 0 ]; do
        if ping -c 3 "$1" >/dev/null; then
                printf '%s\n' "$2"
                shift 2
                while [ "$1" != END ]; do
                        printf '\t%s\n' "$1"
                        shift
                done
        else
                while [ "$1" != END ]; do shift; done
        fi

        shift
done
Related Question