Shell test conditions and exit codes

Tutorials – Shell Scripting

Article from Issue 222/2019

The Bash shell uses different criteria to make decisions. Learn how to teach your shell scripts to make the right choice.

In the previous installment of this series [1], I  described how to add different possible courses of action to a script, so that the script can choose by itself which action to execute while running. In this issue, I will explain how to teach a script to choose which of the available actions to execute.

Often, the real decision-making challenge lies not in figuring out whether your shell script needs a while loop or some nested if statements but rather in determining the conditions that will tell your script when it should stop that loop or which branch of that "if" statement to execute. The main Bash tools for this purpose are a big set of test operators (see the descriptions online [2] [3] [4]) and their corresponding syntax, which can evaluate whether some condition is true or false. By contrast, exit status codes [5] are the traces that built-in commands, or whole scripts, leave behind to communicate their achievements.

In this article you will learn through examples and working code:

  • Which kinds of testing operators are available
  • The syntax of those operators and their related Bash keywords
  • How to retrieve (or provide for further testing) a single command or an entire script's exact outcome via exit codes

Test Conditions and Operators

Shell scripts can check if a condition is verified or not in three main ways: values of numbers, content and structure of text strings, and file properties. In and of themselves, the syntax and operators are not really difficult. They are just very picky and hard to memorize, because they are numerous and as hard to distinguish as they are powerful and useful in practice. For this reason, I highly recommend that you print or save cheat sheets of the resources referenced in this article [2], [3], [4], [5].

When I talk about "test conditions," I mean the code that follows Bash keywords like if, while, or until. There are four different ways to write these test conditions; all are relatively simple, but very picky. The devil really is in the details.

The first and perhaps most common way of writing test conditions is the single-bracket syntax, as follows:

if [ ! -f somefile ]
   # do something

This code means "do something" only if somefile is not a regular file: the -f operator asks if it is true that somefile is a regular file. The exclamation mark before -f negates the statement (i.e., inverts the answer). Of course, the condition inside the brackets may be much more complex, as I will show in a moment.

The other main syntax for Bash test conditions uses two brackets per side and behaves in slightly different ways, which you really want to know in order to exploit the syntax instead of enduring much frustration.

As you already know, before using a variable, the Bash interpreter splits any variable containing spaces into the several words defined by those spaces. This is why this check

if [ $TEST_VARIABLE == 'Hello World' ]
echo "TEST succeeded!"

will fail complaining "[ too many arguments": $TEST_VARIABLE was split into the two separate terms "Hello" and "World", and the string comparison operator == only accepts one term per side. However, just wrap that condition into another pair of brackets as follows

if [[ $TEST_VARIABLE == 'Hello World' ]]
echo "TEST succeeded!"

and it will merrily print "TEST succeeded!": The first effect of double brackets is to disable word splitting on the left term of a condition.

On the right term of the same condition, the double bracket syntax has the opposite effect. Put asterisks into that term

if [[ $TEST_VARIABLE == so*string* ]]
echo "TEST succeeded!"

and the test will succeed. Remove the outer brackets, and it will fail, unless you set $TEST_VARIABLE to be exactly 'so*string*' (without the quotes). The reason is that only the double brackets enable "globbing" on the right term; they make Bash decode any asterisk inside that term not as an ordinary character, but as a wildcard meaning "there could be anything here!" In other words, without globbing, a string like 'so*string*' matches only that exact sequence of 10 characters. With globbing, it includes every string that starts with so and contains the sequence string, with zero or multiple random characters before and after string.

Double brackets also support Perl-style regular expressions to match patterns inside strings:

if [[ $TEST_VARIABLE =~ ^so ]]
echo "TEST succeeded!"

The code above will print "TEST succeeded!", because the right half of the condition above means "match any string that starts with s and o."

Another thing that the two main bracket syntaxes treat differently is filenames. Inside single brackets, *.txt expands to be "all the files in this folder ending with the .txt extension," because the asterisk is interpreted as "zero or multiple characters." Inside double brackets, instead, the asterisk would be taken literally, and *.txt would mean "a file with the .txt extension and one asterisk as name."

The final difference between single and double brackets is that only the single bracket form accepts the -a and -o formats of the logical AND and OR operators, whereas double brackets allow the use of && and || (more on this later). In general, double brackets are trickier, but they are more flexible and better suited to handle text comparisons.

Alternative Syntaxes

In addition to the single and double bracket constructs, you also can check test conditions in Bash with the built-in test command and normal parentheses:

if [ $COUNTER -ge 100 ] ; then...
if test $COUNTER -ge 100; then...
if (( $COUNTER >= 100)); then...

Those three conditions mean the same thing: "If $COUNTER is equal or greater to 100, then do something."

The test option is more compact, but I personally find it a bit less readable than the brackets. On the other hand, the parentheses are very clear but only work on numerical conditions, not with strings or filenames.

Combining Test Conditions

Test conditions would be of very limited use if they could not be combined. Regardless of syntax (single bracket, double bracket, parentheses, or the test command), you can use the -a (AND) operator to denote that two conditions must be satisfied. This is how you tell Bash to do something only if $COUNTER is equal or greater to 100, and $NAME is equal to "Mark":

if [ $COUNTER -ge 100 -a $NAME == 'Mark' ]; then....

The test and brackets constructs can, as already mentioned, also use the && and || versions of the AND and OR operators, while parentheses only support the -a form.

Whatever format of AND and OR you use, remember that unless you use parentheses, AND always takes precedence over OR.

Last but not least, whatever syntax you use or whatever condition you test, never forget that:

  • Quoting variables makes the test work even if there are spaces or newlines inside them.
  • Spaces between brackets and the variables and operators they contain are necessary.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Bash scripting

    A few scripting tricks will help you save time by automating common tasks.

  • Batsh

    Batsh kills two birds with one stone: Programs written in this language can be compiled both as Linux Bash scripts and Windows batch files.

  • Metadata in the Shell

    Armed with the right shell commands, you can quickly identify and evaluate file and directory metadata.

  • SHC: Bash Script Compiler

    The Bash Shell Script Compiler converts shell scripts directly into binaries. Compiling your scripts provides protection against accidental changes, but you will have to contend with some quirks.

  • Bash Alternatives

    Don't let your familiarity with the Bash shell stop you from exploring other options. We take a look at a pair of alternatives that are easy to install and easy to use: Zsh and fish.

comments powered by Disqus