Signals & Trap
Handling SIGTERM, SIGINT, and other signals in Bash scripts with trap, for graceful shutdown and guaranteed cleanup.
What Signals Are (Recap)
Signals are how the OS or another process asks a running process to do something — most commonly, to stop. Scripts can catch most signals with trap and run custom cleanup logic instead of dying immediately.
kill -l # list all available signals
| Signal | Number | Common Trigger | Default Behavior |
|---|---|---|---|
| SIGINT | 2 | Ctrl+C | Terminate |
| SIGTERM | 15 | kill pid (default) | Terminate |
| SIGHUP | 1 | Terminal closed | Terminate |
| SIGKILL | 9 | kill -9 pid | Terminate — cannot be caught by trap |
Basic trap Syntax
trap 'COMMAND' SIGNAL_NAME
trap 'echo "Caught SIGINT, exiting..."; exit 1' INT
trap 'cleanup' TERM
trap 'echo "Script exiting (any reason)"' EXIT
Graceful Shutdown Pattern
#!/usr/bin/env bash
set -euo pipefail
RUNNING=true
cleanup() {
echo "Received shutdown signal — cleaning up..."
RUNNING=false
}
trap cleanup SIGTERM SIGINT
echo "Starting worker loop (PID $$)..."
while [ "$RUNNING" = true ]; do
echo "Processing batch..."
sleep 2
done
echo "Worker stopped cleanly"
./worker.sh &
kill -TERM $! # triggers cleanup() instead of an abrupt kill
trap EXIT: Guaranteed Cleanup
TMPDIR=$(mktemp -d)
LOCKFILE="/tmp/myapp.lock"
touch "$LOCKFILE"
cleanup() {
rm -rf "$TMPDIR" "$LOCKFILE"
echo "Cleaned up temp resources"
}
trap cleanup EXIT # runs on normal exit, error exit (with set -e), OR a caught signal that calls exit
# ... rest of script, even if it fails partway through, cleanup() still runs ...
trap ... EXIT is the single most reliable way to guarantee cleanup — it fires whether the script finishes normally, errors out, or is interrupted (as long as the signal handler itself calls exit).
Ignoring a Signal
trap '' INT # ignore Ctrl+C entirely (empty command = ignore)
echo "This can't be interrupted with Ctrl+C"
sleep 10
trap - INT # restore default SIGINT behavior
Multiple Signals, One Handler
trap cleanup SIGINT SIGTERM SIGHUP # one function handles several signals
Resetting a Trap
trap - EXIT # remove a previously set EXIT trap (revert to default behavior)
A Realistic Example: A Long-Running Sync Script
#!/usr/bin/env bash
set -euo pipefail
PIDFILE="/var/run/sync.pid"
echo $$ > "$PIDFILE"
cleanup() {
echo "Shutting down sync process..."
rm -f "$PIDFILE"
exit 0
}
trap cleanup SIGTERM SIGINT EXIT
while true; do
rsync -a /data/ /backup/
sleep 300
done
Production Considerations
SIGKILL(9) cannot be trapped — never rely on cleanup logic running after akill -9; design for that possibility (e.g., idempotent operations, lock files with staleness checks).- Container orchestrators (Kubernetes, Docker) send
SIGTERMon shutdown and wait a grace period beforeSIGKILL— a script/entrypoint that doesn’t handleSIGTERMgracefully gets forcibly killed, potentially mid-write. trap cleanup EXITshould be set as early as possible in a script, right after acquiring any resource (temp file, lock, PID file) that needs guaranteed cleanup.
Quick Interview Answer
“
trap 'command' SIGNALlets a Bash script intercept signals like SIGTERM/SIGINT and run cleanup logic instead of dying immediately — essential for graceful shutdown in long-running scripts or daemons.trap cleanup EXITis the most reliable pattern, since it fires on normal completion, error exit, or a caught signal, guaranteeing temp files and locks get cleaned up. SIGKILL is the one exception — it can never be trapped, so cleanup logic must not be the ONLY safety net for critical resources.”
Common Mistakes
- Assuming
trapcan catch SIGKILL — it fundamentally cannot, by kernel design. - Not handling SIGTERM in long-running scripts/containers, causing the orchestrator to hard-kill them after the grace period, potentially corrupting in-progress work.
- Setting
trap cleanup EXITtoo late in the script, after resources needing cleanup were already created.
Add More Questions to This Guide
Know a question that should be here? Share it and help the community!
Open Google Form