Compound logical tests in bash

bash, testing

Solution

Little note of interest: The square braces are actually a Unix command. See if there's a file called `/bin/[` or `/usr/bin/[` on your system. In fact, try this:

$ ls i-il /bin[ /bin/test
571541 -r-xr-xr-x  2 root  wheel  43120 Aug 17  2011 /bin/[
571541 -r-xr-xr-x  2 root  wheel  43120 Aug 17  2011 /bin/test

That first number is the i-node number. You see that `/bin/[` and `/bin/test` are hard linked to each other. They're the same program.

I'll get back to the significance of this a bit later...

In BASH and other Bourne type shells, the `if` command merely runs the command, and if the command returns an exit code of `0`, it executes the commands in the `if` clause. If the command does not return an exit code of `0`, it skips the commands in the `if` clause and executes the commands in the `else` clause if it exists.

Thus, this is also valid:

if sleep 2
then
   echo "That was a good nap!"
fi

Your computer executes the `sleep 2` command which (if the sleep command exists) will return a zero exit code and continue to echo That was a good nap!.

That's all the `if` command really does. It runs the given command and examines the exit code.

Now, back to the `[`...

You might want to look at the manpage for the test command. You can see that the `test` command seems to have many of the same type of tests as the `[...]` in the if statement. In fact, in the original Bourne shell (which BASH is derived from) these are the same command structure:

if test -f $my_file
then
    echo "File $my_file exists"
else
    echo "There is no file $my_file"
fi

if [ -f $my_file ]
then
    echo "File $my_file exists"
else
    echo "There is no file $my_file"
fi

In fact, in the original Bourne shell, you had to use the `test` command and not the square brackets.

So, back to your original question. The `-a` and `-o` are parameters for the test command (see the `test` manpage. For example, your `if` statement could have been written as this:

if test $testnum4 -eq 0 -a $testnum100 -ne 0 || test $testnum400 -eq 0
then
   echo 0
fi

However, the `&&` and `||` are not test command parameters, so they can't be used inside the test command.

Note that `[` is a built in command in BASH, so BASH does not execute `/bin/[` as the original Bourne shell would have. However, it is still a command much like `echo` is a built in command in BASH although `/bin/echo` also still exists.

Problem

A question about bash .... I wrote a little code to check whether a year is a leapyear. The relevant part looks like: ``` if [ $testnum4 -eq 0 -a $testnum100 -ne 0 ] || [ $testnum400 -eq 0 ] then echo 0 fi ``` Well, this code actually works. My problem is that I'm still not quite sure what I was doing. Just to see what would happen, I have tried several variations on this: enclosing the entire test expression in square backets, using `&&` where I have `-a` and `-o` where I have `||`, etc. Nothing works except for the one expression that I wrote above. I stumbled on it, and I'm glad I did, but the next time I have to do something like this, I would like to know what I am doing. So: Anyone have a quick explanation of how compound tests work in bash? or a pointer to a good resource on this topic? Thanks. Here's a code that failed: ``` let testnum4=$1%4 let testnum100=$1%100 let testnum400=$1%400 if [ $testnum4 -eq 0 && $testnum100 -ne 0 ] -o [ $testnum400 -eq 0 ] then echo 0 fi ```

Original source