2

I have this script:

#!/bin/bash
ping_1=$(ping -c 1 www.test.com  | tail -1| awk '{print $4}' | cut -d '/' -f 2 | sed 's/\.[^.]*$//')
ping_2=$(ping -c 1 www.test1.com | tail -1| awk '{print $4}' | cut -d '/' -f 2 | sed 's/\.[^.]*$//')
ping_3=$(ping -c 1 www.test2.com | tail -1| awk '{print $4}' | cut -d '/' -f 2 | sed 's/\.[^.]*$//')
ping_4=$(ping -c 1 www.test3.com | tail -1| awk '{print $4}' | cut -d '/' -f 2 | sed 's/\.[^.]*$//' )

Then I would like to treat the outputs of ping_1-4 in one variable. Something like this:

#!/bin/bash

if [ "$ping_*" -gt 50 ]; then
    echo "One ping is to high"
else
    echo "The pings are fine"
fi

Is there a possibility in bash to read these variables with some sort of wildcard?

$ping_*

Did nothing for me.

SiHa
  • 7,830
  • 13
  • 34
  • 43
blox
  • 23
  • 3

5 Answers5

3

The answer to your stated problem is that yes, you can do this with parameter expansion in bash (but not in sh):

#!/bin/bash
ping_1=foo
ping_2=bar
ping_etc=baz
for var in "${!ping_@}"
do
  echo "$var is set to ${!var}"
done

will print

ping_1 is set to foo
ping_2 is set to bar
ping_etc is set to baz

Here's man bash:

   ${!prefix*}
   ${!prefix@}
          Names matching prefix.  Expands to the names of variables  whose
          names begin with prefix, separated by the first character of the
          IFS special variable.  When @ is used and the expansion  appears
          within  double  quotes, each variable name expands to a separate
          word.

The answer to your actual problem is to use arrays instead.

that other guy
  • 116,971
  • 11
  • 170
  • 194
1

I don't think there's such wildcard. But you could use a loop to iterate over values, for example:

exists_too_high() {
    for value; do
        if [ "$value" -gt 50 ]; then
            return 0
        fi
    done
    return 1
}

if exists_too_high "$ping_1" "$ping_2" "$ping_3" "$ping_4"; then
    echo "One ping is to high"
else
    echo "The pings are fine"
fi
janos
  • 120,954
  • 29
  • 226
  • 236
  • Works like intended and your answer is also in my range of understanding! Thank you very much! – blox Oct 30 '16 at 16:40
1

You can use "and" (-a) param:

if [ $ping_1 -gt 50 -a \
     $ping_2 -gt 50 -a \
     $ping_3 -gt 50 -a ]; then
   ...
   ...

Or instead of defining a lot of variables, you can make an array and check with a loop:

pings+=($(ping -c 1 www.test.com | tail -1| awk '{print $4}' | cut -d '/' -f 2 | sed 's/\.[^.]*$//'))
pings+=($(ping -c 1 www.test1.com | tail -1| awk '{print $4}' | cut -d '/' -f 2 | sed 's/\.[^.]*$//'))
pings+=($(ping -c 1 www.test2.com | tail -1| awk '{print $4}' | cut -d '/' -f 2 | sed 's/\.[^.]*$//'))
pings+=($(ping -c 1 www.test3.com | tail -1| awk '{print $4}' | cut -d '/' -f 2 | sed 's/\.[^.]*$//' ))

too_high=0
for ping in ${pings[@]}; do
    if [ $ping -gt 50 ]; then
        too_high=1
        break
    fi
done

if [ $too_high -eq 1 ]; then
    echo "One ping is to high"
else
    echo "The pings are fine"
fi
Lara Maia
  • 124
  • 11
  • ++ for the array approach, but a few quibbles: `-a` is marked as obsolescent due to ambiguity; since Bash is used, use `[[ $ping_1 -gt 50 && ... ]]` instead (or `(( ping_1 > 50 && ... ))`. Using `[[ ... ]]` means you needn't double-quote the LHS of binary operators - which you should do with `[ ... ]`. If the variables only contain numbers, you don't strictly need to double-quote them, but doing so (outside of `[[ ... ]]` / `(( ... ))`) is a good habit to form, including `"${pings[@]}"`. Similarly, capturing command output directly in an array `pings+=( $(...) )` is generally fragile. – mklement0 Oct 30 '16 at 17:27
  • remember that [[ ]] is slower than [ ]. – Lara Maia Oct 30 '16 at 18:36
  • I don't think there are any real-world scenarios where the relative performance of `[ ... ]` vs. `[[ ... ]]` matters, so, given the [added robustness and features offered by `[[ ... ]]`](http://stackoverflow.com/a/29320710/45375), I suggest using the latter, _if_ its presence can be assumed (Bash, Ksh, Zsh). As for the technicality: [This answer](http://stackoverflow.com/a/24194257/45375) of mine looked into the relative performance, and found `[[ ... ]]` to be significantly faster. I invite you to verify the findings and provide feedback if you disagree _there_. – mklement0 Oct 30 '16 at 18:50
  • Some time ago I made a benchmark for [], [[ ]], and (( )). In my tests the (( )) was very significantly faster, and [ ] a bit more faster than [[ ]]. The test was based on a large number of sequential operations. I find it interesting to understand how these mechanisms work. But maybe you're right, I've never tried it in real-world scenarios. – Lara Maia Oct 30 '16 at 21:46
  • The linked answer provides such benchmarks. In short, the findings are that `[[ ... ]]` is fastest, followed by `(( ... ))`, with `[ ... ]`, depending on specifics of the conditional, taking up to almost twice as long as `[[ ... ]]`. Visit the answer, run the benchmarks, and comment _there_, if you get different results and/or find fault with the methodology. _All that said: it probably won't matter in real life - make your choice based on robustness and features._ – mklement0 Oct 31 '16 at 02:08
0

To complement the existing, helpful answers with an array-based solution that demonstrates:

  • several advanced Bash techniques (robust array handling, compound conditionals, handling the case where pinging fails)
  • an optimized way to extract the average timing from ping's output by way of a single sed command (works with both GNU and BSD/macOS sed).
  • reporting the servers that either took too long or failed to respond by name.
#!/usr/bin/env bash

# Determine the servers to ping as an array.
servers=( 'www.test.com' 'www.test1.com' 'www.test2.com' 'www.test3.com' )

# Initialize the array in which timings will be stored, paralleling the
# "${servers[@]}" array.
avgPingTimes=()

# Initialize the array that stores the names of the servers that either took
# too long to respond (on average), or couldn't pe pinged at all.
failingServers=()

# Determine the threshold above which a timing is considered too high, in ms.
# Note that a shell variable should contain at least 1 lowercase character.
kMAX_TIME=50

# Determine how many pings to send per server to calculate the average timing
# from.
kPINGS_PER_SERVER=1

for server in "${servers[@]}"; do

  # Ping the server at hand, extracting the integer portion of the average
  # timing.
  # Note that if pinging fails, $avgPingTime will be empty. 
  avgPingTime="$(ping -c "$kPINGS_PER_SERVER" "$server" |
                   sed -En 's|^.* = [^/]+/([^.]+).+$|\1|p')"

  # Check if the most recent ping failed or took too long and add
  # the server to the failure array, if so.
  [[ -z $avgPingTime || $avgPingTime -gt $kMAX_TIME ]] && failingServers+=( "$server" )

  # Add the timing to the output array.
  avgPingTimes+=( "$avgPingTime" )

done

if [[ -n $failingServers ]]; then # pinging at least 1 server took too long or failed
  echo "${#failingServers[@]} of the ${#servers[@]} servers took too long or couldn't be pinged:"
  printf '%s\n' "${failingServers[@]}"
else
  echo "All ${#servers[@]} servers responded to pings in a timely fashion."
fi
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    Thanks for the effort. I already learned more from your script than in the last 4 hours surfing the web. – blox Oct 30 '16 at 20:03
0

Yes bash can list variables that begin with $ping_, by using its internal compgen -v command, (see man bash under SHELL BUILTIN COMMANDS), i.e.:

for f in `compgen -v ping_` foo ; do 
    eval p=\$$f
    if [ "$p" -gt 50 ]; then
        echo "One ping is too high"
        break 1
    fi
    [ $f=foo ] && echo "The pings are fine"
done

Note the added loop item foo -- if the loop gets through all the variables, then print "the pings are fine".

agc
  • 7,973
  • 2
  • 29
  • 50