Bash Tipps and Tricks

Useful Defaults

For bash script reliability, it is often useful to set a few preferences at the top of the file. These help to prevent certain types of pitfalls in more complex scripts and minimize the impact of errors.

I put the following at the begining of many of my scripts

set -euo pipefail

The above is essentially the same as saying

set -e -u -o pipefail

Error handling in scripts involves processing the Exit statues of the commands that are called . In POSIX terms, a command signals the success or failure of its execution with a number between 0 and 255. Any non-zero number signals some sort of error.

set -e Exit immediately if a command exits with a non-zero status

This is useful for scripts which require every one of their commands to succeed in order to produce some meaningful end result. If it doesn't make sense for the script to continue when a command has failed, set -e should be included at the top.

While this helps to catch many problems, it does not detect when a command that is piping its output to another command fails, as long as the last command in the pipeline does not exit with an error.

set -o pipefail Cause the whole pipeline to fail if one of its commands fail

Quoting the set --help documentation, this does the following:

the return value of a pipeline is the status of the last command to exit with a non-zero status, or zero if no command exited with a non-zero status

set -u Treat unset variables as an error when substituting

This simply catches typos in variables.

set -x Print commands and their arguments as they are executed

Many scripts don't give much indication about their execution progress, so it can sometimes be hard to track down the precise point at which a failure occured. This is where set -x comes in handy

Dependency checking

Some of my scripts that rely on non-standard tools start like this:

#!/usr/bin/env bash
set -euo pipefail

function installed { which "$1" > /dev/null 2>&1; }

function die { >&2 echo "Fatal: ${@}"; exit 1; }

deps=(openssl sed grep)

for dep in "${deps[@]}"; do
    installed "${dep}" || die "Missing '${dep}'"
done

Handling background processes

A very crude way to manage two processes where one can't meaningfully operate without the other: Start both of them in the background and keep track of their Process IDs (PIDs). If one of the processes terminates, kill the other.

./bin/some_service &
pid1=$!
./bin/some_other_service &
pid2=$!
# terminate script if either command terminates
wait -n $pid1 $pid2
# send kill signal to both pids since we don't know which one terminated
kill $pid1 $pid2

References