Shell test conditions and exit codes
Tutorials – Shell Scripting
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 ] then # 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
TEST_VARIABLE='Hello World' if [ $TEST_VARIABLE == 'Hello World' ] then echo "TEST succeeded!" fi
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
TEST_VARIABLE='Hello World' if [[ $TEST_VARIABLE == 'Hello World' ]] then echo "TEST succeeded!" fi
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
TEST_VARIABLE='somestring' if [[ $TEST_VARIABLE == so*string* ]] then echo "TEST succeeded!" fi
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:
TEST_VARIABLE='so*string*' if [[ $TEST_VARIABLE =~ ^so ]] then echo "TEST succeeded!" fi
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
(incl. VAT)
Buy Linux Magazine
Direct Download
Read full article as PDF:
Price $2.95
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you've found an article to be beneficial.
News
-
Armbian 23.05 is Now Available
Based on Debian 12, the latest version of the ARM/RISC-V distribution is now available to download and install.
-
Linux Mint Finally Receiving Support for Gestures
If you use the Linux Mint Cinnamon desktop, you'll be thrilled to know that 21.2 is getting support for gestures on touchscreen devices and touchpads.
-
An All-Snap Version of Ubuntu is In The Works
Along with the standard deb version of the open-source operating system, Canonical will release an-all snap version.
-
Mageia 9 Beta 2 Ready for Testing
The latest beta of the popular Mageia distribution now includes the latest kernel and plenty of updated applications.
-
KDE Plasma 6 Looks to Bring Basic HDR Support
The KWin piece of KDE Plasma now has HDR support and color management geared for the 6.0 release.
-
Bodhi Linux 7.0 Beta Ready for Testing
The latest iteration of the Bohdi Linux distribution is now available for those who want to experience what's in store and for testing purposes.
-
Changes Coming to Ubuntu PPA Usage
The way you manage Personal Package Archives will be changing with the release of Ubuntu 23.10.
-
AlmaLinux 9.2 Now Available for Download
AlmaLinux has been released and provides a free alternative to upstream Red Hat Enterprise Linux.
-
An Immutable Version of Fedora Is Under Consideration
For anyone who's a fan of using immutable versions of Linux, the Fedora team is currently considering adding a new spin called Fedora Onyx.
-
New Release of Br OS Includes ChatGPT Integration
Br OS 23.04 is now available and is geared specifically toward web content creation.