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 empty
fi

### Or this
if [ "x$VAR" == x ]; then
    echo empty
fi

### 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 empty
fi

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 way
fi

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.