6

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

0

3 Answers 3

7

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
6
  • Many thanks. "for variable in A B ; do typeset -n n="$variable" " did the trick
    – dg72
    Commented Mar 17, 2021 at 14:42
  • Why should not use it just like this? A=( "127.0.0.1" "localhost" "aaa nnn cvcc" ) B=( "8.8.8.8" "dns" "bbb tttt rrrr") for variable in A B ; do typeset -n n="$variable" #need to declare it explicitily as a variable if ping -c3 ${n[0]};then echo "${n[1]}" for share in ${n[2]};do echo $share done fi done
    – dg72
    Commented Mar 17, 2021 at 14:43
  • 1
    @DavidGalligani Well, you can, but you are explicitly using the shell to split the third element on whitespaces. The shell will then use each generated word and apply filename globbing on them. This is generally an unsafe thing to do and one usually want to avoid it. If you are 100% sure that you will never have words that will split at the wrong positions, and will never invoke filename globbing, then I suppose you can do what you want. Since the question says nothing about the words in that part of the array, I decided to play it safe instead.
    – Kusalananda
    Commented Mar 17, 2021 at 16:33
  • thanks for your clarification. The only reason I did that was that in this way I can use more elements and is clear what is what, third being always the shares.
    – dg72
    Commented Mar 19, 2021 at 8:39
  • 1
    Yes. That was my point. I understand in your code shares come always last, and that if I was to use another element ( i.e. description) I have to put it before the shares.
    – dg72
    Commented Mar 19, 2021 at 8:48
5

This answer doesn't address your question directly but rather presents an alternative solution to the problem. It doesn't rely on arrays.

Instead of dealing with arrays, I would use a file, which IMO has the advantage of keeping the data apart from the program. Easier to write and maintain.

For this to work seaparate the "shares" by comma , or other character:

file.txt

127.0.0.1 localhost aaa,nnn,cvcc
8.8.8.8  dns bbb,tttt,rrrr
failedip  failed a,b,c

The script:

#!/bin/bash

# loop over the file's lines
while IFS=' ' read -r ip domain share; do
  # a blank line to separate results at each loop
  echo
  # perform the `ping`, redirect to `/dev/null` if you don't need to print the output
  if ping -c3 "$ip" &> /dev/null;then
    echo "ping to $ip successful"
    echo "domain: $domain"
    # change the IFS to comma ',' in a subshell to parse the 'shares'
    (IFS=,; printf "share: %s\n" $share)
  else
    # message if `ping` failed
    echo "ping to $ip failed"
  fi
done < file.txt

Output:


ping to 127.0.0.1 successful
domain: localhost
share: aaa
share: nnn
share: cvcc

ping to 8.8.8.8 successful
domain: dns
share: bbb
share: tttt
share: rrrr

ping to failedip failed
1
  • Thank you, this is interesting. The only thing is that I wanted to keep everything in the same file for simplicity, but now I'm thinking about it...
    – dg72
    Commented Mar 22, 2021 at 8:26
4
for n in ${A} ${B} ;  do

This doesn't do what you want. Taking the array without an index is the same as taking the element at index zero, so ${A} is just the same as ${A[0]}, and same for B. The loop sets $n to the individual values one by one, so you get ${A[0]} on the first iteration, and ${B[0]} on the second.

The same applies with ${n[0]}, ${n[1]}, ${n[2]} etc. $n isn't an array, so only ${n[0]} has any content, the others are just unset.

Now, Bash doesn't really do 2D-arrays, but you could invert that structure with something like this:

ips=(127.0.0.1 8.8.8.8)
names=(localhost dns)
for i in "${!ips[@]}"; do
   if ping -c3 "${ips[i]}"; then
       echo "host ${names[i]-"(unknown")} is up"
   fi
done

But you probably should just store the data in a separate file anyway.


Note that all of that about the data structures and looping may well be different in Zsh, and IIRC ksh also has better data structure support than Bash.

You must log in to answer this question.

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