Bash Control Structures Conditional execution of command lists


How to use conditional execution of command lists

Any builtin command, expression, or function, as well as any external command or script can be executed conditionally using the &&(and) and ||(or) operators.

For example, this will only print the current directory if the cd command was successful.

cd my_directory && pwd

Likewise, this will exit if the cd command fails, preventing catastrophe:

cd my_directory || exit
rm -rf *

When combining multiple statements in this manner, it's important to remember that (unlike many C-style languages) these operators have no precedence and are left-associative.

Thus, this statement will work as expected...

cd my_directory && pwd || echo "No such directory"
  • If the cd succeeds, the && pwd executes and the current working directory name is printed. Unless pwd fails (a rarity) the || echo ... will not be executed.
  • If the cd fails, the && pwd will be skipped and the || echo ... will run.

But this will not (if you're thinking if...then...else)...

cd my_directory && ls || echo "No such directory"
  • If the cd fails, the && ls is skipped and the || echo ... is executed.
  • If the cd succeeds, the && ls is executed.
    • If the ls succeeds, the || echo ... is ignored. (so far so good)
    • BUT... if the ls fails, the || echo ... will also be executed.

      It is the ls, not the cd, that is the previous command.

Why use conditional execution of command lists

Conditional execution is a hair faster than if...then but its main advantage is allowing functions and scripts to exit early, or "short circuit".

Unlike many languages like C where memory is explicitly allocated for structs and variables and such (and thus must be deallocated), bash handles this under the covers. In most cases, we don't have to clean up anything before leaving the function. A return statement will deallocate everything local to the function and pickup execution at the return address on the stack.

Returning from functions or exiting scripts as soon as possible can thus significantly improve performance and reduce system load by avoiding the unnecessary execution of code. For example...

my_function () {


    # one argument required. "" evaluates to false(1)
    [[ "$1" ]]             || return 1

    # work with the argument. exit on failure
    do_something_with "$1" || return 1
    do_something_else      || return 1

    # Success! no failures detected, or we wouldn't be here
    return 0