How can I group expressions in an if statement, e.g. if (A AND B) OR C?

The portable (POSIX or Bourne) way is to use multiple test (or [) commands:

Toggle line numbers
   1 # Bourne
   2 if commandA && commandB || commandC; then
   3 ...
   4 
   5 # or with test(1) calls:
   6 if [ testA ] && [ testB ] || [ testC ]; then
   7 ...

When they are shell operators between commands (as opposed to the [[...]] operators), && and || have equal precedence, so processing is left to right.

If we need explicit grouping, then we can use curly braces:

Toggle line numbers
   1 # Bourne
   2 if commandA && { commandB || commandC; }; then
   3 ...

What we should not do is try to use the -a or -o operators of the test command, because the results are undefined.

BASH, zsh and the KornShell have different, more powerful comparison commands with slightly different (easier) quoting:

Examples:

Toggle line numbers
   1 # Bash/ksh/zsh
   2 if (( (n>0 && n<10) || n == -1 ))
   3 then echo "0 < $n < 10, or n==-1"
   4 fi

or

Toggle line numbers
   1 # Bash/ksh/zsh
   2 if [[ ( -f $localconfig && -f $globalconfig ) || -n $noconfig ]]
   3 then echo "configuration ok (or not used)"
   4 fi

Note that contrary to the && and || shell operators, the && operator in ((...)) and [[...]] has precedence over the || operator (same goes for ['s -a over -o), so for instance:

Toggle line numbers
   1 [ a = a ] || [ b = c ] && [ c = d ]

is false because it's like:

Toggle line numbers
   1 { [ a = a ] || [ b = c ]; } && [ c = d ]

(left to right association, no precedence), while

Toggle line numbers
   1 [[ a = a || b = c && c = d ]]

is true because it's like:

Toggle line numbers
   1 [[ a = a || ( b = c && c = d ) ]]

(&& has precedence over ||).

Note that the distinction between numeric and string comparisons is strict. Consider the following example:

Toggle line numbers
   1 n=3
   2 if [[ $n>0 && $n<10 ]]
   3 then echo "$n is between 0 and 10"
   4 else echo "ERROR: invalid number: $n"
   5 fi

The output will be "ERROR: ....", because in a string comparison "3" is bigger than "10", because "3" already comes after "1", and the next character "0" is not considered. Changing the square brackets to double parentheses (( )) makes the example work as expected.


CategoryShell