Chapter 11Lesson 04~95 minutes

trap, Cleanup, and Signal-Aware Scripts

Resources acquired by a script need a lifecycle. Traps connect shell exit and signals to cleanup logic, but they work best when status preservation, child ownership, and untrappable failure are part of the design.

IntermediateError handlingHands-on lab

Learning objectives

By the end of this lesson

  • Register reliable EXIT cleanup.
  • Preserve failure status during cleanup.
  • Separate signal response from final cleanup.
  • Terminate owned child processes.
  • Design for failures that bypass traps.

1. trap binds cleanup logic to lifecycle events

cleanup() {
  printf 'cleanup\n' >&2
}
trap cleanup EXIT

The EXIT pseudo-signal is the common foundation for temporary-resource cleanup. Signal traps such as TERM and INT can add graceful termination behavior.

2. Preserve the incoming exit status

cleanup() {
  local status=$?
  rm -f -- "${tmp:-}"
  exit "$status"
}
trap cleanup EXIT

Without saving the incoming status first, cleanup commands can replace the status that caused the exit.

3. Register cleanup immediately after acquisition

tmpdir=$(mktemp -d) || exit 1
trap 'rm -rf -- "$tmpdir"' EXIT

The earlier cleanup is registered, the fewer later error paths can leak the resource.

4. Cleanup functions are easier to maintain than dense trap strings

tmpdir=""
child_pid=""

cleanup() {
  local status=$?

  [[ -n $child_pid ]] &&
    kill -TERM "$child_pid" 2>/dev/null || true

  [[ -n $tmpdir && -d $tmpdir ]] &&
    rm -rf -- "$tmpdir"

  exit "$status"
}

trap cleanup EXIT

Functions provide normal local variables, branches, comments, and testing opportunities.

5. Separate signal response from final cleanup

on_term() {
  printf 'termination requested\n' >&2
  exit 143
}

trap on_term TERM INT
trap cleanup EXIT

The signal handler decides why to exit; the EXIT handler performs shared cleanup once.

6. Owned child processes belong in cleanup policy

child_pid=""

cleanup() {
  local status=$?
  if [[ -n $child_pid ]]; then
    kill -TERM "$child_pid" 2>/dev/null || true
    wait "$child_pid" 2>/dev/null || true
  fi
  exit "$status"
}

This avoids leaving background work running after the parent exits unexpectedly.

7. Cleanup must tolerate partial initialization

cleanup() {
  local status=$?

  if [[ -n ${tmpdir:-} && -d $tmpdir ]]; then
    rm -rf -- "$tmpdir"
  fi

  if [[ -n ${lockfile:-} && -e $lockfile ]]; then
    rm -f -- "$lockfile"
  fi

  exit "$status"
}

A script may fail before every resource exists. Cleanup should safely handle that state.

8. ERR traps are not a universal exception hook

trap 'printf "ERR status=%d command=%s\n" "$?" "$BASH_COMMAND" >&2' ERR
Use with care

ERR has context rules related to errexit, functions, subshells, and conditionals. It is useful for diagnostics but should not replace explicit error paths.

9. RETURN and DEBUG traps are specialized tools

Bash also supports traps such as DEBUG and RETURN. They are useful for tracing and instrumentation but add complexity and should not be part of ordinary application logic without a clear reason.

10. Cleanup is best effort, not guaranteed

SIGKILL, power loss, kernel failure, or runtime termination can bypass traps completely.

Recovery matters

Locks, temp files, and checkpoints should tolerate stale state. Cleanup is a convenience and safety layer, not an absolute guarantee.

11. Hands-on lab: signal-aware temporary workspace

mkdir -p "$HOME/devops-academy/bash/chapter11/lesson04"
cd "$HOME/devops-academy/bash/chapter11/lesson04"

cat > worker.sh <<'EOF'
#!/usr/bin/env bash
set -u

tmpdir=$(mktemp -d) || exit 1
child_pid=""

cleanup() {
  local status=$?

  if [[ -n $child_pid ]]; then
    kill -TERM "$child_pid" 2>/dev/null || true
    wait "$child_pid" 2>/dev/null || true
  fi

  rm -rf -- "$tmpdir"
  exit "$status"
}

on_term() {
  printf 'termination requested\n' >&2
  exit 143
}

trap cleanup EXIT
trap on_term TERM INT

bash -c 'while :; do sleep 1; done' &
child_pid=$!

printf 'tmpdir=%s child=%s parent=%s\n' "$tmpdir" "$child_pid" "$$"
wait "$child_pid"
EOF

chmod u+x worker.sh
printf 'Run ./worker.sh, then send TERM to its parent PID to test cleanup.\n'

Verification checklist

12. Knowledge check

Question 1. Why save $? at the beginning of cleanup?

Question 2. Why should cleanup be idempotent?

Question 3. Can an EXIT trap handle SIGKILL?

Question 4. Should ERR be treated as a universal exception mechanism?

13. Summary

Traps connect resource cleanup and process shutdown to shell lifecycle. Preserve status, register cleanup early, track child processes, separate signal response from shared cleanup, and design recovery for the cases where traps never run.

14. Further reading

  • GNU Bash Reference Manual — Traps and Signals.
  • GNU Bash Reference Manual — ERR, DEBUG, and RETURN.
  • Linux signal(7).
  • ShellCheck documentation — trap and cleanup patterns.
Next lesson

Designing Explicit Error Paths and Diagnostics

Continue Chapter 11 by making Bash failure behavior more explicit and predictable.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.