The catch-all cleanup
trap 'echo cleaning up; rm -f "$lock"' EXITtrap CMD SIGNAL... installs shell handling for named signals. EXIT runs when that shell exits normally or through a failure it can process. It cannot run after SIGKILL, power loss, or process replacement by a successful exec.
Common patterns
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT
# ... use $tmpdir freely; it's auto-cleaned
# Lock file
lock=/tmp/myjob.lock
[[ -e "$lock" ]] && { echo 'already running'; exit 1; }
echo $$ > "$lock"
trap 'rm -f "$lock"' EXITMultiple signals
# Make cleanup idempotent; let EXIT perform it once
cleanup() { rm -f -- "$lock"; }
trap 'exit 130' INT
trap 'exit 143' TERM
trap cleanup EXIT
# Different handlers
trap 'echo "caught Ctrl+C"' INT
trap 'echo "normal exit"' EXITReset / clear
trap - INT restores the default INT handler (terminate). trap '' INT ignores the signal entirely.
Variables in trap
Quote carefully: trap "rm -f $tmpdir" EXIT expands $tmpdir now, when the trap is set. trap 'rm -f "$tmpdir"' EXIT with single quotes expands later, when the trap fires — usually what you want, especially if the variable might change.
Recovery completes the cleanup design
Use private temporary directories, narrow validated paths, and idempotent handlers. If stale locks or partial output can survive an uncatchable failure, validate and recover them when the next run starts.
A lock file alone does not prove ownership
A stale file can outlive its process, and a reused PID can identify something else. Prefer an atomic locking primitive and validate ownership and recovery behavior on startup.