3

I have a problem with the sh script syntax. I wrote a script for my Asus router. The script works perfectly. But I have this line:

if [[ "$OldIP" && "$StartIP" != "$OldIP" ]]; then echo OK; fi

It should be true ( and execute echo OK) only if $StartIP and $OldIP are not the same. The line works, but I would like to see it done more efficiently. The variables conatain valid IP addresses.

In some instances, $OldIP will not be assigned anything (is not initialized). But, if $OldIP does not exist, this means they are not the same in my shell!

I do not want the line to do: if $OldIP does not exist -> test if they are different -> run echo OK.

I do want the line to mean: a) if $OldIP does not exist -> end. plus b) if $OldIP exists -> test if they are different -> run echo OK.

So, I would like to remove "$OLDIP" && somehow, if possible. Not a real problem; curious to learn :)

Sort of (but it does not work):

if [ [ "$OldIP" ] != "$StartIP" ]; then echo OK; fi

or

if [ $OldIP != "$StartIP" ]; then echo OK; fi

which does what I want, but complains when OldIP is empty (but works OK)

while

if [ "$OldIP" != "$StartIP" ]; then echo OK; fi

works but ignores that OldIP is empty

  • You have two conditions that need to be checked, you need them both logically. However, many languages (including all shell scripts that i know of) will "short-circuit" the logical operators. If "$OLDIP" evaluated to false, the remainder of the expression won't be evaluated since it won't change the logic being tested. – user1794469 May 18 '16 at 15:05
  • This sound like premature optimization. Have you profiled the script to make sure that this is really the bottleneck? – Raphael Ahrens May 18 '16 at 15:16
  • Purely curiosity issue. I would like statement to be: if [[ "$OldIP" != "$StartIP" ]]; then echo OK; fi but it ignores non existance state of OldtIP – Pila May 18 '16 at 16:01
2

Strictly speaking, a variable does not exist if it has never been assigned a value, or if it has been unset. A variable with an empty string as value exists.

The standard parameter expansion ${variable+value} will substitute the string value when variable exists (is not unset or empty). This could be used to test for existence like this:

if [ "${OldIP+x}" = "x" ] && [ "$OldIP" != "$StartIP" ]; then
    # code
fi

To test whether OldIP exists in bash, use the -v test:

 if [[ -v OldIP ]] && [[ "$OldIP" != "$StartIP" ]]; then
     # code
 fi

This would perform the string comparison between $OldIP and $StartIP only if OldIP had been previously set (even if it was set to an empty string). Note that the -v test takes the name of the variable.

  • busybox sh doesn't support -v. You can use [ "${OldIP+set}" = set ] there. – Stéphane Chazelas Jul 1 '18 at 7:01
  • @StéphaneChazelas Sorry, that's me not reading the tags and assuming this was for bash. My bad. Thanks for the suggestion. – Kusalananda Jul 1 '18 at 7:03
0

I think your script is pretty efficient as is. You certainly aren't spending many cycles on this.

Another way to write the logic:

{ test -n "$OldIP" || test "$StartIP" != "$OldIP"; } && echo OK

This says "if OldIP is set or if OldIP and StartIP are different, then echo OK.

Remember that [ is another name for test(1) which you can read about with man 1 test.

Edit: As Gilles pointed out, take care to avoid creating subshells ( ... ) where they're not needed.

{ ... ; } can be used to group commands without executing a new shell.

Brief reference: http://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html

  • 1
    Creating a subshell reduces the performance. { test … || test …; } && echo OK would do the same thing without creating a subshell. It wouldn't be a definite performance improvement over the original (maybe a slight difference one way or the other depending on the shell0. – Gilles May 18 '16 at 21:47
  • Thank you Gilles, how ironic I would actually make it slower! I've updated my answer to reflect your hint. – ssh2ksh May 19 '16 at 8:28
0

This does what you ask for:

[ "${OldIP:-"$StartIP"}" != "$StartIP" ] && echo "OK"

As also this (more complex):

${OldIP:+false} || { [[ $OldIP != $StartIP ]] && echo "OK"; }
0

If you are using bash, you can use a default substitution:

[[ "${OldIP:=oldunset}" != "${StartIP?StartUnset" ]] && echo "OK"

The syntax ${var:-def} will evaluate to either the current value of $var, or to the specified default (in this case, def) if the variable is unset or null. The value of the variable (if any) is unchanged.

The syntax ${var?message} will exit with an error code of 1 with a message of message if $var is unset or null.

If you explicitly need test-compatible tests, you can do this:

[ ! -z "$OldIP" -a "$OldIP" != "$StartIP" ] && echo "OK"
  • 2
    ! -z "STRING" is the same as -n "STRING" (or just "STRING") – cas May 18 '16 at 21:21
  • Your proposal with ${OldIP:=unset} fails the value of StartIP is unset. If these are IP addresses as the name suggests, this won't arise, but it makes the code fragile: what if the script supports host names one day? – Gilles May 18 '16 at 21:49
  • Easily fixed. I shall do so forthwith. – DopeGhoti May 20 '16 at 17:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.