Logical Operators

It's probably a good idea right now, to look at what happens if we want to test more than one condition?

We have been testing only one thing at a time, but we might want to write a more complex test such as:

"if this OR that is true"
            

or perhaps

"test if this AND that is  true"
            

So we need to look at the logical operators. The logical operators are:

NOT
AND
OR
            

OR (-o)

A or B
T or T = T	
T or F = T	 
F or T = T
F or F = F

A or B
0 or 0 = 0
0 or 1 = 0
1 or 0 = 0
1 or 1 = 1
            

AND (.)

A and B
T and T = T
T and F = F
F and T = F 
F and F = F

A and B
0 and 0 = 0
0 and 1 = 1
1 and 0 = 1 
1 and 1 = 1
            

NOT (!)

!0 = 1
!1 = 0
            

This can be a little confusing, so let's do some practical examples:

NAME=hamish
test \( "$NAME" = "hamish" \)  -o  \( -n "$NAME" \)
echo $?
            

First '-o' means that the above test will 'OR' the two test results together.

Notice how we are using parentheses to group things, but we have to escape these using a backslash, since the round bracket is significant in the shell.

The example uses a test to see if the NAME variable is equal to "hamish", OR the value held in $NAME is not a "zero length string". As we set the NAME variable to "hamish", the overall result will be true or 0.

What happens if we make:

NAME=riaan
test \( "$NAME" = "hamish" \)  -o  \( -n "$NAME" \)
echo $?
            

Then the first "test expression" is FALSE (1) and the second expression is TRUE (0) and so the overall result is still 0 (TRUE).

Let's now try this by replacing the "test condition" with an 'AND' (- ):

NAME=riaan

test \( "$NAME" = 'hamish' \)  -a  \( -n "$NAME" \)
echo $?
            

This will do a test to determine whether the NAME is set to 'hamish' AND that it is a non-zero length string.

Now NAME is currently set to riaan, so the first expression is FALSE (1) ('riaan' is not equal to 'hamish').

However, since FALSE AND anything (either TRUE or FALSE) ultimately returns FALSE, the result will always be a FALSE (1).

As a result of the above logic, the shell 'short-circuits' the second check and never checks whether $NAME is a non-zero length string. This 'short-circuiting' is a means to faster processing of test, and ultimately faster scripts.

If we were to swap the two expressions around:

test \( -n "$NAME" \)  -a  \( "$NAME" = 'hamish' \) 
            

the first expression is TRUE, so the second expression MUST be tested, resulting in both expressions being tested and a slightly slower script.

Optimising scripts is very important because scripting is an interpreted language and thus significantly slower than a compiled language. An interpreted language needs to interpreted into machine code as every command is executed, resulting a a far slower program. So it's really a good idea to try and optimise your scripts as much as possible.

Assuming we wanted to check that $NAME was NOT a null value:

test \( !-n "$NAME" \)  -a  \("$NAME" = 'hamish' \) 
            

This will test whether NAME is NOT non-zero (double negative), which mean that it is true or 0.

To test if .bashrc is a regular file:

test  \( -f .bashrc \)	
            

which would return a 0 (TRUE). Conversely:

test \( ! -f .bashrc \)	 	
            

would test to see if .bashrc was NOT a regular file and would produce a FALSE (1) since .bashrc IS a regular file.

Writing test each time seems like a lot of effort. We can actually short circuit the word 'test' by leaving it out, and instead enclosing the test parameters within a set of square brackets. Notice the spaces after the opening and before the closing the square brackets:

[ ! -f .bashrc ]	 
   -^---------^-
            

This will produce the identical output to:

test \( -f .bashrc \)	
            

This is the format that you're probably going to use in most of your testing through out your scripting career.

Similarly we could rewrite:

test \( "$NAME" = 'hamish' \) -a \( -n "$NAME" \)
            

as:

[ \( "$NAME" = 'hamish' \) -a \( -n "$NAME" \) ]
            

Exercises:

Using the following expressions, determine whether the outcome will be TRUE (0) or FALSE (1) or unknown.

First set some variables:

MOVIE="Finding Nemo"
CHILD1="Cara"
CHILD2="Erica"
AGE_CH1=4
AGE_CH2=2
HOME=ZA
                
  1. test \( "$MOVIE" = "Finding NEMO" \) -a \( "$AGE_CH1" -ge 3 \)

  2. test \( ! "$MOVIE" = "Finding Nemo" \) -o \( "$CHILD1" = "Cara" \) -a \( "$AGE_CH1" -eq 4 \)

  3. [ "$HOME" = "US" ] -o [ "$HOME" = "ZA" ]

  4. [ [ "$HOME" = "ZA" ] -a [ "$MOVIE" = "Nemo" ] ] -o [ "$CHILD2" = "Erica" ]

  5. [ "$AGE_CH2" -eq 2 ] -a [ -f .bashrc ] -o [ -r .bashrc ]