DevOps Tutorial · Linux and the Shell · Lesson 8 of 25
Writing Robust Scripts
Make bash scripts fail fast and clean up after themselves with set -euo pipefail, traps, quoting and ShellCheck.
Fail fast
By default bash keeps going after a failed command, which is dangerous in automation: a failed cd followed
by rm -rf * deletes the wrong directory. Start scripts with:
#!/usr/bin/env bash
set -euo pipefail
-eexit when a command fails.-utreat unset variables as errors (catches typos like$DEPLOY_DIRR).-o pipefaila pipeline fails if any command in it fails, not just the last.
cat > without.sh <<'SCRIPT'
false | true
echo "without pipefail: the failed pipeline was ignored"
SCRIPT
cat > with.sh <<'SCRIPT'
set -euo pipefail
false | true
echo "never printed"
SCRIPT
bash without.sh
bash with.sh || echo "with pipefail: script stopped (exit code $?)"
bash -c 'set -u; echo "$UNDEFINED_VAR"' 2>&1 | sed 's/^.*line [0-9]*: //' || true
Output
without pipefail: the failed pipeline was ignored with pipefail: script stopped (exit code 1) UNDEFINED_VAR: unbound variable
Run this on your computer — see “Get Started” for setup.
Clean up with trap
trap ... EXIT runs whatever happens, so temporary files and locks are always removed.
cat > job.sh <<'SCRIPT'
set -euo pipefail
workdir=$(mktemp -d)
trap 'rm -rf "$workdir"; echo "cleaned up temp dir"' EXIT
echo "working in a temp dir"
false # something fails half-way
echo "never printed"
SCRIPT
bash job.sh || echo "job failed with exit code $?, but cleanup still ran"
Output
working in a temp dir cleaned up temp dir job failed with exit code 1, but cleanup still ran
Run this on your computer — see “Get Started” for setup.
More habits
- Quote every variable:
"$var","${array[@]}". - Use
mktempfor temporary files, never fixed names in/tmp. - Check scripts with ShellCheck (
shellcheck deploy.sh) in CI; it catches most quoting and logic bugs. - When a script grows past a page or needs data structures, switch to Python.
Test yourself
In a script with set -o pipefail, what is the exit status of false | true?
0 (success)Non-zero (failure)It depends on the shellThe pipeline never finishes
Show answer
Answer: Non-zero (failure)
Without pipefail only the last command counts; with it, any failing command fails the pipeline.