trap: cleanup and signals

Bash · Advanced · 10 min

Real scripts create temp files, lock files and background processes that must be cleaned up even on failure.

trap 'commands' SIGNALS registers code to run when the shell receives a signal or reaches a special event. The most useful is the EXIT pseudo-signal, which fires whenever the script ends: normally, through exit N, or because set -e aborted it. A single EXIT trap is the bash equivalent of finally.

The standard pattern: create resources with mktemp, then immediately register the trap.

  • Put cleanup in a function and register it with trap cleanup EXIT.
  • Read $? as the first thing in the function to know whether the script failed.
  • Use rm -rf -- "$tmpdir"; the -- guards against names starting with a dash.

Other events: INT (Ctrl-C), TERM (polite kill from systemd, Docker or kill), HUP (terminal closed), and the bash pseudo-signals ERR (a command failed) and DEBUG (before every command). By convention a script interrupted by a signal exits with 128 + the signal number, so 130 after Ctrl-C.

trap - INT restores the default and trap '' INT ignores the signal. trap -p shows current traps. SIGKILL (9) can never be trapped, so no cleanup runs after kill -9; design temp files to be harmless if left behind. Traps are not inherited by subshells in the same way, so set them in the process that owns the resources.

Example

cd "$(mktemp -d)"
cat > job.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
tmpdir=$(mktemp -d)
cleanup() {
  local status=$?
  rm -rf -- "$tmpdir"
  echo "cleaned up (exit status $status)" >&2
}
trap cleanup EXIT              # normal exit, error, or exit N
trap 'echo "interrupted" >&2; exit 130' INT TERM
echo "working in $tmpdir"
touch "$tmpdir/data"
false                          # set -e aborts here...
echo "not reached"
EOF
bash job.sh
# working in /tmp/tmp.XXXX
# cleaned up (exit status 1)

Remember

  • trap cleanup EXIT runs on every exit path: the shell's finally
  • Trap INT and TERM to exit cleanly with 128 + signal
  • SIGKILL cannot be trapped

Watch out for

  • Registering the trap after creating several resources, so an early failure leaks the first ones.

Check yourself

  1. Which trap runs both on a normal end and when set -e aborts the script?
  2. A process gets `kill -9`. What happens to its EXIT trap?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.gnu.org/software/bash/manual/bash.html#Bourne-Shell-Builtins

Builds on

Unlocks

Nothing builds on this yet.