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"
cd
succeeds, the && pwd
executes and the current working directory name is printed. Unless pwd
fails (a rarity) the || echo ...
will not be executed.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"
cd
fails, the && ls
is skipped and the || echo ...
is executed.cd
succeeds, the && ls
is executed.
ls
succeeds, the || echo ...
is ignored. (so far so good)ls
fails, the || echo ...
will also be executed.
It is the
ls
, not thecd
, that is the previous command.
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 () {
### ALWAYS CHECK THE RETURN CODE
# 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
}