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