1

So I have an array like:

al_ap_version=('ap_version' '[[ $data -ne $version ]]')

And the condition gets evaluated inside a loop like:

for alert in alert_list; do
    data=$(tail -1 somefile)
    condition=$(eval echo \${$alert[1]})
    if eval "$condition" ; then
        echo SomeAlert
    fi
done

Whilst this generally works with many scenarios, if $data returns something like "-/-" or "4.2.9", I get errors as it doesn't seem to like complex strings in the variable.

Obviously I can't enclose the variable in single quotes as it won't expand so I'm after any ideas to expand the $data variable (or indeed the $version var which suffers the same possible fate) in a way that the evaluation can handle?

3
  • The fact that you are using eval is a red flag here, and can probably be avoided. What does alert_list look like? Commented May 21, 2014 at 12:40
  • 1
    Also, why don't you use != instead of -ne? Commented May 21, 2014 at 12:49
  • I thought it might, but not sure how to get around it. alert_list contains the list of arrays that are to be processed, e.g. alert_list="al_ap_version al_os_dsk". Commented May 21, 2014 at 12:53

2 Answers 2

1

Ignoring the fact that eval is probably super dangerous to use here (unless the data in somefile is controlled by you and only you), there are a few issues to fix in your example code.

In your for loop, alert_list needs to be $alert_list.

Also, as pointed out by @choroba, you should be using != instead of -ne since your input isn't always an integer.

Finally, while debugging, you can add set -x to the top of your script, or add -x to the end of your shebang line to enable verbose output (helps to determine how bash is expanding your variables).

This works for me:

#!/bin/bash -x

data=2.2
version=1

al_ap_version=('ap_version' '[[ $data != $version ]]')
alert_list='al_ap_version'

for alert in $alert_list; do
    condition=$(eval echo \${$alert[1]})
    if eval "$condition"; then
        echo "alert"
    fi
done
Sign up to request clarification or add additional context in comments.

Comments

1

You could try a more functional approach, even though bash is only just barely capable of such things. On the whole, it is usually a lot easier to pack an action to be executed into a bash function and refer to it with the name of the function, than to try to maintain the action as a string to be evaluated.

But first, the use of an array of names of arrays is awkward. Let's get rid of it.

It's not clear to me the point of element 0, ap_version, in the array al_ap_version but I suppose it has something to do with error messages. If the order of alert processing isn't important, you could replace the list of names of arrays with a single associative array:

declare -A alert_list
alert_list[ap_version]=... # see below
alert_list[os_dsk]=...

and then process them with:

for alert_name in ${!alert_list[@]}; do
  alert=${alert_list[$alert_name]}
  ...
done

Having done that, we can get rid of the eval, with its consequent ugly necessity for juggling quotes, by creating a bash function for each alert:

check_ap_version() {
  (($version != $1))
}

Edit: It seems that $1 is not necessarily numeric, so it would be better to use a non-numeric comparison, although exact version match might not be what you're after either. So perhaps it would be better to use:

check_ap_version() {
  [[ $version != $1 ]]
}

Note the convention that the first argument of the function is the data value.

Now we can insert the name of the function into the alert array, and call it indirectly in the loop:

declare -A alert_list
alert_list[ap_version]=check_ap_version
alert_list[os_dsk]=check_op_dsk

check_alerts() {
  local alert_name alert
  local data=$(tail -1 somefile)
  for alert_name in ${!alert_list[@]}; do
    alert=${alert_list[$alert_name]}
    if $alert "$data"; then
      signal_alert $alert_name
    fi
  done
}

If you're prepared to be more disciplined about the function names, you can avoid the associative array, and thereby process the alerts in order. Suppose, for example, that every function has the name check_<alert_name>. Then the above could be:

alert_list=(ap_version os_dsk)

check_alerts() {
  local alert_name
  local data=$(tail -1 somefile)
  for alert_name in $alert_list[@]; do
    if check_$alert_name "$data"; then
      signal_alert $alert_name
    fi
  done
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.