Skip to content

$ cat ./posts/bash/traps-signals-and-cleanup-that-actually-runs.sh

-rw-r--r-- 1.7K #bash #systemd

Traps, signals and cleanup that actually runs

by chris · 2 min read · /bash


A script that creates a temporary directory and deletes it on the last line is a script that leaks a temporary directory. Anything between the two lines — a failure, a Ctrl-C, a systemd stop — skips the cleanup entirely.

1. One trap, set immediately

TMP="$(mktemp -d)"
cleanup() {
  rm -rf "$TMP"
}
trap cleanup EXIT

EXIT fires on a normal exit, on an error under set -e, and after an interrupt handler returns. Set it on the line after the resource is created, never later.

2. What EXIT does not cover

SIGKILL cannot be trapped, by design. So the cleanup must also be idempotent and, for anything that matters, recoverable from outside the script: a stale lock file needs an owner PID inside it so the next run can tell a crash from a peer.

trap 'cleanup; exit 130' INT
trap 'cleanup; exit 143' TERM

Handling INT and TERM explicitly preserves the conventional exit codes, which matters when a supervisor is deciding whether to restart you.

3. Under systemd

A unit gets SIGTERM, then SIGKILL after TimeoutStopSec. If cleanup takes longer than that, it does not finish. Say so in the unit rather than hoping:

[Service]
ExecStart=/usr/local/bin/sync-archive
TimeoutStopSec=90
KillMode=mixed

KillMode=mixed sends the term signal to the main process only, giving the script the chance to shut its children down in order. The default kills the whole cgroup at once, which is exactly what a careful trap handler is trying to avoid.

← cd .. fris@linux:~/blog$ man bash