How to Use the trap Command in Bash Scripts for Clean Exits in Linux

When running Bash scripts in Linux, unexpected interruptions are a common reality. Users press Ctrl + C to cancel a task, system daemons issue termination signals during reboot, or individual commands fail mid-execution. Without proactive signal handling, interrupted scripts leave behind orphaned temporary directories, lingering lock files, and zombie child processes. The built-in trap command in Bash provides an elegant mechanism to intercept signals and guarantee that necessary cleanup routines execute reliably every time your script terminates.

Understanding Linux Signals and Bash Pseudo-Signals

In Unix-like operating systems, the kernel and user processes communicate state changes by sending asynchronous notifications known as signals. When a signal arrives, the recipient process halts its normal execution flow to run a designated signal handler. Bash allows you to attach custom commands or functions to standard POSIX signals as well as special shell-specific pseudo-signals:

Signal Name Number / Identifier Common Cause / Trigger
EXIT 0 (Pseudo-signal) Fires whenever the script terminates, regardless of whether it completed successfully, encountered an error, or received an intercepted signal.
SIGINT 2 Sent when an interactive user presses Ctrl + C in the terminal to interrupt the running process.
SIGTERM 15 The standard termination signal sent by process managers, systemd, or the kill command to request a graceful shutdown.
SIGHUP 1 Generated when the controlling terminal or SSH session closes abruptly.
ERR Pseudo-signal Triggers whenever any command within the script returns a non-zero exit code (similar to set -e behaviour).

Basic Syntax of the trap Command

The standard syntax for setting up a signal trap in Bash requires an action (either an inline command string or a function name) followed by one or more target signals:

trap 'commands_to_execute' SIGNAL_NAME [ANOTHER_SIGNAL ...]

To inspect all currently active traps registered in your shell environment, run trap without any arguments:

trap -p

Building a Robust Cleanup Routine Using trap EXIT

The most idiomatic, maintainable approach to script sanitisation is binding a dedicated cleanup function to the EXIT pseudo-signal. Because EXIT fires whenever the shell process concludes, your cleanup routine runs automatically whether the script finishes its tasks normally or exits early via exit 1.

Here is a complete, production-grade template demonstrating temporary file sanitisation and exit code preservation:

#!/usr/bin/env bash
set -euo pipefail

# Create a secure temporary scratchpad directory
WORK_DIR=$(mktemp -d -t myapp-script.XXXXXXXXXX)

# Define our cleanup handler
cleanup() {
    local exit_code=$?
    echo "Performing automated cleanup of: ${WORK_DIR}"
    rm -rf "${WORK_DIR}"
    exit "${exit_code}"
}

# Register the trap for EXIT, SIGINT, and SIGTERM
trap cleanup EXIT SIGINT SIGTERM

echo "Script initialised. Working inside ${WORK_DIR}"
# Simulate heavy processing or file generation
echo "Processing payload..." > "${WORK_DIR}/output.log"
sleep 5

echo "Execution finished successfully."

If you run this script and press Ctrl + C during the five-second sleep, the shell immediately invokes cleanup(), purges the temporary directory from /tmp, and restores a tidy filesystem.

Catching Signal Interrupts to Terminate Background Children

Scripts that spawn long-running background tasks using & frequently leave orphaned processes consuming CPU cycles when the parent script is interrupted. You can configure your trap handler to propagate the shutdown signal to all spawned child tasks using kill 0 (which signals the entire process group):

#!/usr/bin/env bash

# Function to safely terminate all child processes
terminate_children() {
    echo "Cancellation received! Terminating background workers..."
    trap - SIGINT SIGTERM # Prevent recursive signal loops
    kill -- -$$ 2>/dev/null || true
    exit 130
}

trap terminate_children SIGINT SIGTERM

# Launch multiple background worker jobs
worker_one &
worker_two &

# Wait for background jobs to conclude
wait

Resetting and Disabling Signal Traps

In complex deployment scripts, you may want to disable a trap once a critical or irreversible phase has completed, or restore default kernel handling:

  • Reset to Default Behaviour: Passing a hyphen (-) as the action clears the custom handler and restores system default signal handling:
    trap - SIGINT SIGTERM
  • Ignore a Signal Entirely: Providing an empty string ('') instructs the shell to ignore the signal completely:
    trap '' SIGINT
    This is particularly useful when executing a database migration or disk formatting step where an accidental Ctrl + C keystroke would cause data corruption.

Best Practices and Considerations

When implementing signal traps across production Linux automation, keep these fundamental guidelines in mind:

  • Uncatchable Signals: Neither SIGKILL (signal 9) nor SIGSTOP can be trapped, caught, or ignored by user-space programmes. If the kernel Out-Of-Memory (OOM) killer or an administrator issues kill -9, your trap handler will not run.
  • Keep Handlers Lightweight: Signal handlers should execute swiftly. Avoid long interactive prompts, complex network calls, or lengthy calculations inside your cleanup function.
  • Subshell Inheritance: Subshells spawned with parentheses ( ... ) do not inherit traps set in the parent shell, with the exception of ignored signals. If a subshell creates its own temporary resources, declare a local trap within that subshell block.

Get the best tech tips delivered straight to your inbox.

Join thousands of readers mastering Apple, Google, Microsoft, and Linux.