Chapter 1. A Big âifâ Idiom
To get you started in understanding bash idioms, weâll look at a bash construct that allows you to do what you might normally do with an if
/then
/else
construct but with sparser syntax. The idiomatic expression weâll show you in this chapter has some real advantagesâmostly tersenessâand some pitfalls to avoid. But if you donât know bash idioms, you might not recognize or realize what is going on.
Take a look at this piece of code:
[[
-n"
$DIR
"
]]
&&
cd
"
$DIR
"
Do you think that looks like an if
statement? If youâre conversant in bash, youâll recognize that it is functionally the same thing; youâll understand it as an if
statement even though the if
keyword doesnât appear in the code.
Whatâs going on?
The Big âifâ
To explain this idiom, letâs first look at a similar but simpler example:
cd
tmp&&
rm scratchfile
This too, is, in effect, an if
statement. If the cd
command succeeds, then (and only then) execute the rm
command. The âidiomâ here is the use of the double ampersand (&&
), typically read as âand,â to separate the two commands.
Logic or philosophy classes teach the rule: the expression âA AND Bâ is true if and only if both A and B are each true. Therefore if A is false, there is no need to even consider the value of B. For example, consider âI own a dog AND I own a cat.â If I do not own a dog, then this compound expression is false for me, regardless of my cat situation.
Letâs apply this to bash. Remember that the basic function of bash is to execute programs. The first part of this statement has the cd
command execute. Similar to the logic of the AND, if this first command fails, bash will not bother to execute the second part, the rm
command.
The use of the &&
is meant to remind you of the AND behavior. Bash isnât actually performing an âANDâ operation on these two results. (If this were C/C++, that would be a different story, though the conditional execution is the same.) However, this bash idiom does provide the conditional execution of the second commandâit isnât run if the first command fails.
Letâs go back and look at the original example we gave, namely this expression:
[[
-n"
$DIR
"
]]
&&
cd
"
$DIR
"
Do you think you âget itâ now? The first expression tests to see if the length of the value of the variable called DIR
is nonzero. If it has a value, that is, if its length is nonzeroâand only if itâs nonzeroâthe cd
command will attempt to change into a directory named by the value of DIR
.
We could have written this as an explicit if
statement:
if
[[
-n"
$DIR
"
]]
;
then
cd
"
$DIR
"
fi
To someone less familiar with bash, this latter format is certainly more readable and easily understood. But there isnât much going on inside the then
clause, just the cd
command, so it seems a bit âbulkyâ in its syntax. Youâll have to decide which to use based on who your readers will likely be and how likely other commands would be added inside the then
clause.
Weâll offer more opinions on this topic in the sections that follow.
bash Help
The bash help
command is great for any builtin command, but help test
provides especially helpful clues about the test expression, like -n
. You might also check man bash
, but if you do, youâll want to search for âconditional expressions.â The bash man
page is very long; help
thing has much shorter, focused topics. If you are not sure if the command in question is a bash builtin, just try it with the help command, or use type -a
thing to find out.
Admittedly, knowing that help test
will tell you what -n
means is tricky, but then, you were smart enough to buy this book, so now you know. Here is another subtle little tidbit; go try this: help [
. Youâre welcome.
Or ELSEâ¦
A similar idiom is available using the ||
characters to separate two items in a bash list. Pronounced âor,â the second part will execute only if the first part fails. This is meant to remind you of the logic rule for âOR,â as in: A OR B. The whole expression is true if either A is true or B is true. Put another way, if A is true, it doesnât matter if B is true or false. For example, consider the phrase âI own a dog OR I own a cat.â If I do, in fact, own a dog, then this expression is true for me regardless of my cat situation.
Applying this to bash:
[[
-z"
$DIR
"
]]
||
cd
"
$DIR
"
Do you think you can explain this one? If the variable is of length zero, then the first part is âtrue,â so there is no need to execute the second half; no cd
command will be run. But if the length of $DIR
is nonzero, the test would return âfalse,â so only then would we run the cd
command.
You might read the line of bash as âEither $DIR
is zero length, OR we attempt to cd
into that directory.â
To write that as an explicit if
statement is a bit odd, as there is no then
action to be taken. The code after the ||
is like the else
clause:
if
[[
-z"
$DIR
"
]]
;
then
:else
cd
"
$DIR
"
fi
The ":
" is a null statement in shellâso it does nothing in that case.
In summary:
two commands separated by an &&
are like an if
and its then
clause;
two commands separated by a ||
are like an if
and its else
clause.
More than One
You might want to do more than one thing in this else
-like clause after the ||
(or in a then
-like clause after an &&
) and therein lies a danger. It might be tempting to write something like this:
# Caution: not what you might think!
cd
/tmp||
echo
"cd to /tmp failed."
;
exit
The âorâ connection tells us that if the cd
fails we will execute the echo
command, telling the user that the cd
failed. But hereâs the catch: the exit
will happen regardless. Not what you expected, right?
Think of the semicolon as equivalent to a newline, and it all becomes much clearer (and more obviously not what you wanted):
cd
/tmp||
echo
"cd to /tmp failed."
exit
How can we get the behavior we want? We can group the echo
and exit
together so that they are, taken together, the clause on the righthand side of the âor,â like this:
# Succeed with the cd or bail with a message
cd
/tmp||
{
echo
"cd to /tmp failed."
;
exit
;
}
The braces are bash syntax for a compound command, i.e., grouping statements together. You may have seen something similar using parentheses, but using parentheses executes the statements in a subshell, also referred to as a child process. That would incur an overhead we donât need, and the exit occurring from within a subshell wouldnât accomplish much either.
Closing Compound Commands
A quirk of bash requires a specific syntax for how you close the compound command. It must end with either a semicolon or a newline before the closing brace. If you use a semicolon, there needs to be whitespace before the brace so that the brace can be recognized as a reserved word (otherwise it gets confused with the closing brace of shell variable syntax, as in ${VAR}
, for example). That is why the preceding example ends with what looks to be an extraneous semicolon: { echo "â¦" ; exit ; }
. Using a newline, that final semicolon isnât needed:
# Succeed with the cd or bail with a message
cd
/tmp||
{
echo
"cd to /tmp failed."
;
exit
}
but that might not read as cleanly. At the left edge, it seems oddly placed; indented with whitespace, it groups more logically but seems bare.
We recommend you stick with the extra semicolon, and donât forget the space between it and the closing brace.
More than One Again
What if you need more complex logic? What about multiple AND and OR constructs? How are they handled? What do you think the following line of code will do?
[
-n"
$DIR
"
]
&&
[
-d"
$DIR
"
]
&&
cd
"
$DIR
"
||
exit
4
If the DIR
variable is not empty and the file named by the DIR
variable is a directory, then it will cd
to that directory; otherwise it will exit from the script, returning a 4.
That does what you might have expectedâbut maybe not for the reason you think. You might, from this example, be tempted to think that the &&
has higher precedence than the ||
operator, but it doesnât. Theyâre just grouping from left to right. The syntax for bash says that the &&
and ||
operators are of equal precedence and are left associative. Need convincing? Look at these examples:
$echo
1
&&
echo
2
||
echo
3
1
2
$
but also:
$echo
1
||
echo
2
&&
echo
3
1
3
$
Notice that it always evaluates the leftmost operator regardless of whether itâs AND or OR; itâs not operator precedence but simple left associativity that determines the order of evaluation.
Donât Do This
While weâre on the subject of if
statements (or how not to use them), hereâs an example of an explicit if
that you may see quite often in older scripts.
We show it to you here so that we can explain the idiom but also to urge you never to imitate this style. Hereâs the code:
### Don't write your if statements to check like this
if
[
$VAR
"X"
=
X]
;
then
echo
emptyfi
### Or this
if
[
"x
$VAR
"
==
x]
;
then
echo
emptyfi
### Or other variations on this theme
Donât do this.
What are they doing in this code?
Theyâre checking to see if the variable VAR
is empty.
They do that by appending the value of VAR
with some character (here X
).
If the resulting string matches just the letter itself, then the variable was empty.
Donât do this.
There are better ways to make this check. Hereâs a simple alternative:
# Is the length of the variable zero? i.e., empty or null
if
[[
-z"
$VAR
"
]]
;
then
echo
emptyfi
Single Versus Double Brackets
This example of what not to do uses the single brackets, [
and ]
, to surround the condition that they are testing.
Thatâs not what weâre asking you to avoid.
We want you to avoid the string append and comparison; instead, use the -z
or -n
to make these tests.
So why have our examples all used double brackets, [[
and ]]
, for our if
(and non-if
) statements?
They are an addition to bash (not in the original sh
command), and they avoid some confusing edge case behaviors that single brackets exhibit (variable inside quotes or not).
We showed this example with single brackets as this type of comparison is often seen in older scripts.
You may need to stay with single brackets, if your goal is portability across various platforms and/or to non-bash platforms (e.g., dash).
As a side note, the double brackets are keywords, whereas the left single bracket is a builtin, a difference that may explain some subtle differences in behaviors.
Our advice remains to use the double-bracket syntax except when unavoidable.
You can check for the opposite case, checking to see if the length of the string is nonzero, by using the -n
option or by just referencing the variable:
# This checks for a nonzero length, i.e., not empty, not null
if
[[
-n"
$VAR
"
]]
;
then
echo
"VAR has a value:"
$VAR
fi
# Same here
if
[[
"
$VAR
"
]]
;
then
echo
even easier this wayfi
So you see there is no need to use that other approach, which was necessary in legacy versions of the test command (â[â) that are rarely used anymore. We thought you ought to see it, though, so youâll recognize it in older scripts. Now you also know a better way to write it.
Style and Readability: Recap
In this chapter we took a look at a particular bash idiomâthe âno-ifâ if
statement.
It doesnât look like a traditional if
/then
/else
, but it can behave exactly like one.
Unless this is something you recognize, some scripts that you read might remain obscure.
This idiom is also worth using to check that any necessary preconditions are in place before executing a command, or to make a short error check without disrupting the flow of the main logic of the script.
By using &&
and ||
operators, you can write if
/then
/else
logic without the use of those familiar keywords.
But bash does have if
, then
, and else
as keywords.
So when do you use them, and when do you use the shorthand?
The answer comes down to readability.
For complex logic, it makes the most sense to use the familiar keywords. But, for simple test and check situations with single actions, using the &&
and ||
operators can be very convenient and will not distract from the main flow of logic. Use help test
to remind you which tests you can use, like -n
-r
, and consider copying the help text into a comment for the future.
In either case, for familiar if
statements or idiomatic âno-ifâ statements, we encourage the use of the double-bracket syntax.
Now that youâve seen one bash idiom in depth, letâs take a look at others and really up your bash game.
Get bash Idioms now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.