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
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
-
Fedora Asahi Remix 41 Available for Apple Silicon
If you have an Apple Silicon Mac and you're hoping to install Fedora, you're in luck because the latest release supports the M1 and M2 chips.
-
Systemd Fixes Bug While Facing New Challenger in GNU Shepherd
The systemd developers have fixed a really nasty bug amid the release of the new GNU Shepherd init system.
-
AlmaLinux 10.0 Beta Released
The AlmaLinux OS Foundation has announced the availability of AlmaLinux 10.0 Beta ("Purple Lion") for all supported devices with significant changes.
-
Gnome 47.2 Now Available
Gnome 47.2 is now available for general use but don't expect much in the way of newness, as this is all about improvements and bug fixes.
-
Latest Cinnamon Desktop Releases with a Bold New Look
Just in time for the holidays, the developer of the Cinnamon desktop has shipped a new release to help spice up your eggnog with new features and a new look.
-
Armbian 24.11 Released with Expanded Hardware Support
If you've been waiting for Armbian to support OrangePi 5 Max and Radxa ROCK 5B+, the wait is over.
-
SUSE Renames Several Products for Better Name Recognition
SUSE has been a very powerful player in the European market, but it knows it must branch out to gain serious traction. Will a name change do the trick?
-
ESET Discovers New Linux Malware
WolfsBane is an all-in-one malware that has hit the Linux operating system and includes a dropper, a launcher, and a backdoor.
-
New Linux Kernel Patch Allows Forcing a CPU Mitigation
Even when CPU mitigations can consume precious CPU cycles, it might not be a bad idea to allow users to enable them, even if your machine isn't vulnerable.
-
Red Hat Enterprise Linux 9.5 Released
Notify your friends, loved ones, and colleagues that the latest version of RHEL is available with plenty of enhancements.