What trap Does
The trap builtin registers a command or function to run when the shell receives a specified signal, letting scripts intercept events like Ctrl-C (SIGINT), termination requests (SIGTERM), or even bash's own pseudo-signals such as EXIT (fires on any script exit, normal or otherwise) and ERR (fires whenever a command returns non-zero, useful alongside set -e). This turns a script from something that can leave temp files, locks, or background processes behind into one that cleans up deterministically no matter how it terminates.
Cricket analogy: Just as a captain has a rehearsed response the moment rain starts falling mid-over — covers on, players off — trap lets a script have a rehearsed response the instant a signal like SIGINT arrives, rather than being caught off guard.
#!/usr/bin/env bash
set -euo pipefail
TMPDIR=$(mktemp -d)
LOCKFILE=/tmp/myscript.lock
cleanup() {
local exit_code=$?
echo "Cleaning up (exit code $exit_code)..." >&2
rm -rf "$TMPDIR"
rm -f "$LOCKFILE"
exit "$exit_code"
}
# Fires on normal exit, error exit, or receipt of INT/TERM
trap cleanup EXIT
trap 'echo "Interrupted by user"; exit 130' INT
trap 'echo "Terminated"; exit 143' TERM
touch "$LOCKFILE"
echo "Working in $TMPDIR ..."
sleep 30Common Signals and the EXIT/ERR Pseudo-Signals
SIGINT (2) is what Ctrl-C sends and is typically caught to allow a graceful abort message; SIGTERM (15) is the default signal sent by kill and orchestrators like systemd or Kubernetes during shutdown, and should be handled to flush buffers or release resources within the grace period before SIGKILL (9) arrives, which cannot be trapped at all because it bypasses the process's signal handling entirely. Bash's EXIT pseudo-signal fires on any script termination path — whether it falls off the end, calls exit, or dies from an untrapped signal — making it the single best place to put idempotent cleanup logic instead of duplicating it across every possible exit point.
Cricket analogy: SIGKILL is like a match being abandoned outright by the ground authority with zero notice to either team, whereas SIGTERM is a rain delay warning that gives the umpires and players a few minutes to cover the pitch and get to safety.
In containerized environments, PID 1 does not get default signal handlers unless it explicitly traps them, and an unhandled SIGTERM sent to a bash script running as PID 1 in a container is ignored entirely, causing docker stop to hang until the 10-second grace period expires and Kubernetes/Docker escalates to SIGKILL — always trap SIGTERM explicitly in container entrypoints.
trap with ERR and DEBUG
trap 'handler' ERR runs whenever a command returns a non-zero exit status, similar to set -e but observable, and is commonly used to log the failing line number via $LINENO and $BASH_COMMAND before the script exits — note ERR does not fire inside conditions of if, while, until, or commands connected with &&/||, matching set -e semantics. trap 'handler' DEBUG is a more aggressive tool that fires before every simple command, useful for tracing execution or implementing custom timeouts, but it adds meaningful overhead and is normally reserved for debugging sessions rather than production scripts.
Cricket analogy: An ERR trap is like the third umpire automatically reviewing every dismissal appeal only when it's genuinely contested, while a DEBUG trap is like reviewing literally every single ball bowled in the match, technically possible but far more overhead than needed.
Trap handlers registered with single quotes, like trap 'echo $var' EXIT, defer expansion of $var until the trap fires, capturing its value at signal time; using double quotes instead expands $var immediately when trap is called, which is usually not what you want for variables that change during the script.
trap 'command' SIGNALregisters a handler;trap 'command' EXITfires on any script termination path.- SIGKILL (9) can never be trapped or ignored; SIGTERM (15) is the polite request that should be trapped for graceful shutdown.
- PID 1 in a container does not get default signal dispositions — always trap SIGTERM explicitly in entrypoint scripts.
trap ... ERRmirrorsset -esemantics: it skips commands inside conditionals and short-circuit&&/||chains.trap ... DEBUGfires before every simple command and is powerful for tracing but too expensive for production use.- Use single quotes around trap commands so variables are expanded when the signal fires, not when
trapis registered. - Combine
$?,$LINENO, and$BASH_COMMANDinside trap handlers to log exactly what failed and where.
Practice what you learned
1. Which signal can never be trapped or ignored by a bash script?
2. Why is `trap cleanup EXIT` generally preferred over placing cleanup calls before every `exit` statement?
3. What happens if a bash script runs as PID 1 inside a container without trapping SIGTERM?
4. In `trap 'echo $var' EXIT`, when is `$var` expanded?
Was this page helpful?
You May Also Like
Background Jobs and Process Management
Learn how to launch, monitor, and control background jobs in bash using &, jobs, fg, bg, wait, and disown for scripts that need concurrency.
Writing Daemons in Bash
Learn the practical patterns for turning a bash script into a well-behaved background daemon: detaching from the terminal, PID files, logging, signal handling, and systemd integration.
Subshells and Command Grouping
Learn how bash isolates execution with subshells using parentheses versus grouping commands in the current shell with braces, and why the distinction matters for variables, cd, and exit status.
Related Reading
Related Study Notes in DevOps
Browse all study notesNginx Study Notes
DevOps · 30 topics
DevOpsAnsible Study Notes
DevOps · 30 topics
DevOpsAdvanced Kubernetes Study Notes
Kubernetes · 30 topics
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics
DevOpsCI/CD Tools & Pipelines Study Notes
YAML · 37 topics