A bash script killed mid-execution (Ctrl-C, a systemd stop, a Kubernetes pod receiving a SIGTERM) often leaves a temp file, a lockfile, or an unreleased resource behind, unless the script explicitly intercepts the signal before the interruption happens.

What trap actually does

trap associates a command or function with a given signal: when that signal arrives, bash runs that command before continuing (or stopping) the script’s normal execution. Without trap, a signal like SIGTERM or SIGINT terminates the script immediately, with no chance for any cleanup at all.

#!/usr/bin/env bash
LOCKFILE=/tmp/my-script.lock
touch "$LOCKFILE"

# Cleans up the lockfile regardless of which signal arrives,
# before the script actually terminates
trap 'rm -f "$LOCKFILE"' SIGTERM SIGINT EXIT

# ... long-running work ...
sleep 300

EXIT: the pseudo-signal people often forget

EXIT isn’t a real Unix signal, but bash treats it as one inside trap: the associated command runs on every script exit, whether normal (the script finishing) or triggered by a signal caught elsewhere. A single trap ... EXIT is therefore often enough to cover cleanup without listing every possible signal individually, as long as that signal isn’t one of the ones that can’t be intercepted.

What can never be intercepted

SIGKILL (kill -9) and SIGSTOP cannot be caught by any trap, a kernel limitation rather than a bash one: these two signals terminate or suspend the process directly, giving it no opportunity to run any code, including a cleanup that was properly defined. A lockfile that survives a kill -9 isn’t a script bug: it’s the expected, documented behavior of that specific signal.

# No trap can intercept this,
# the cleanup will never run
kill -9 $PID

Where to place the trap in the script

A trap defined after a resource is created (the lockfile, in the earlier example) leaves a window where a signal received before that line triggers no cleanup, since no trap is active yet. Placing the trap immediately after creating each resource that needs cleanup, rather than once at the top of the script, keeps that window as small as possible.

Takeaway

trap associates a command with a signal, letting a script clean up its resources before terminating due to a SIGTERM, a SIGINT, or a normal exit covered by the EXIT pseudo-signal. SIGKILL and SIGSTOP remain impossible to intercept by nature, a kernel limitation that explains why a lockfile sometimes survives a kill -9 with no fault in the script. Placing the trap as close as possible to each resource’s creation, rather than once at the top, keeps the window during which an early signal could still escape the planned cleanup as small as possible.