Some ancillary Bash tips - 9 (HPR Show 2639)

Dave Morriss


Table of Contents

Making decisions in Bash

This is my ninth contribution to the Bash Scripting series under the heading of Bash Tips. The previous episodes are listed below in the Links section.

It seems to me that it would be worthwhile looking at how Bash can be used to make decisions, such as how many times a loop should cycle (looping constructs) or to choose between multiple choices (conditional constructs). Of course we need to look at some of the expressions used in conjunction with the commands that do these tasks – the tests themselves – and we’ll do this in this episode.

This is a complex area which I had some trouble with when I first started using Bash, and there is a lot to say about it all. I have prepared a group of HPR shows about this subject, in order to do it justice, and this is the first of the group.

Types of test

There are four main types of test that can be used to make decisions in Bash:

  • expressions with the test command and the '[' and ']' operators
  • shell arithmetic
  • expressions with the '[[' and ']]' operators
  • Bash functions, commands and programs (in certain contexts)

Note that in some examples in this episode we are using commands which haven’t been properly explained yet, but we will be covering them later in this group of shows.

The '[' and ']' operators and the test command

Using '[ expression ]' or 'test expression' is the same (except that in the former case both square brackets are required). These are built-in commands inherited by Bash from its predecessor the Bourne Shell. See the GNU Manual section on builtins for all of the details. The single square brackets and the test command are standard across other shells that confirm to the POSIX standard, so it is important to know about them.

Note that since '[' and ']' are considered to be equivalent to commands they must be separated from their arguments by at least one space. It is a common error for Bash beginners to write something like:

if [$i -gt 0]; then
    echo "Positive"
fi

This results in an error such as:

bash: [2: command not found

This is because the left square bracket and the value of variable 'i' have been concatenated since there is no intervening space.

The expression between square brackets (or following test) is a Conditional Expression, which is well documented in the GNU Bash Manual section. We will be looking these later in this group of shows.

Shell arithmetic

We looked at some aspects of this subject in show 1951. This show covered arithmetic expansion and mentioned the arithmetic expressions available in Bash. The type of arithmetic expansion expression we looked at was:

$ echo $((42/5))
8

There is also a form consisting of:

(( expression ))

Note that Bash does not care whether you use spaces before and after the expression.

It is possible to assign values and to compare them in this expression. When being used as a test expression the numeric result is used to obtain a true/false value:

  • if the value of the expression is non-zero, the return status is 0 (true)
  • otherwise the return status is 1 (false)

The following downloadable example (bash9_ex1.sh) demonstrates how this type of test behaves:

$ cat bash9_ex1.sh
#!/bin/bash

#
# Demonstration of the arithmetic expressions as tests
#

for i in {-3..3}; do
    echo -n "$i: "
    if ((i)); then
        echo "$? true"
    else
        echo "$? false"
    fi
done

exit
$ ./bash9_ex1.sh
-3: 0 true
-2: 0 true
-1: 0 true
0: 1 false
1: 0 true
2: 0 true
3: 0 true

All values except for zero return a result of 0 (true).

The expression in the double parentheses can be a numeric assignment:

((x = 42))

This would be regarded as true in a test since 42 is non-zero.

The expression may also be a comparison:

((x == 42))

This expression will have a non-zero value and will return the result zero (true), so in a test it can be used to check the value of an integer variable.

There are also Boolean (logical) operators such as && (and) and || so it is possible to write an expression such as:

((x == 42 && y != 42))

This will be true when x is 42 and y is not.

The arithmetic expressions section of the GNU Bash Manual lists all of the arithmetic operators available in Bash.

The '[[' and ']]' operators

We have seen the use of single square brackets (and the test command), but Bash offers an alternative using double square brackets as in:

[[ expression ]]

This form, often referred to as the extended test, also returns a status of zero or 1 after evaluating the conditional expression expression. The possible conditional expressions are documented in the GNU Bash Manual, and we will look at these later.

Just as with the single left square bracket the double bracket is a command (actually it’s a keyword but the way it’s used is the same), so it must be followed by a space. Spaces are also required before the closing bracket or brackets.

One of the differences between [ expression ] and [[ expression ]] is that with the former you should enclose the left operand in double quotes, as in:

[ "$n" -eq 42 ]

The reason is that if variable n is null or empty and there are no quotes the expression will become:

[ -eq 42 ]

This is illegal.

However, with the double bracket form omitting the quotes is accepted:

[[ $n -eq 42 ]]

The way in which the contents of these double brackets are processed by Bash is different; to quote the GNU Bash manual:

Word splitting and filename expansion are not performed on the words between the '[[' and ']]'; tilde expansion, parameter and variable expansion, arithmetic expansion, command substitution, process substitution, and quote removal are performed.

We covered nearly all of these expansion topics in earlier episodes of Bash Tips.

Bash functions, commands and programs

So far I have not done any episodes describing how to write Bash functions, though there have been several more advanced episodes talking about some useful functions I have written for use in scripts. Functions are able to return status values, and such functions can be used as test commands to control some of the commands mentioned. See the yes_no function discussed in episode 2096 for an example that can be used in tests.

Commands like grep can be used in a test:

if grep -q -e '^banana$' fruit.txt; then
    echo "Found a banana"
fi

Here the -q option prevents grep from reporting anything, it just returns a status. The string ‘banana’ is being searched for in the file fruit.txt. If found then grep will exit with a zero status, in other words, a true value and this will then cause the if command to execute the echo command.

It is also possible to run a script or compiled program (possibly one that you have written) in the same way. This relies on the script or program returning a status that signifies success or failure. Most installed tools do this, as we saw with grep.

Bash itself provides two commands true and false which may be programs (perhaps /bin/true and /bin/false) or, more likely, built-in commands, depending on the operating system version you are using. In my case I find that both exist:

$ which true false
/bin/true
/bin/false
$ type true false
true is a shell builtin
false is a shell builtin

The which command finds programs and the type command reports what type of thing a command is.

The true and false commands return true and false status values, in the way explained below. The builtin version will normally be the preferred one.

In the next episode on Looping Constructs and Conditional Constructs the components marked test_commands used in the syntax descriptions are expressions that return a status of 0 (true) or non-zero (false) in the way just explained.

Understanding the status of a command

It is important to understand the concept of the return status of a command. The command:

$ echo "99"

displays the number 99 but returns a status of zero, which denotes the value true. This status is held in the special Bash variable $? which can be tested or displayed. Note that, every command resets the status, so the original value is easily lost:

$ echo "99"
99
$ echo $?
0

If the command fails then a false (non-zero) value will returned:

$ echo (
bash: syntax error near unexpected token `newline'
$ echo $?
2

Here we see that the false command generates a status of 1 (false) but the act of looking at $? changes its previous contents:

$ false
$ echo $?
1
$ echo $?
0