Table of Contents
- What Are Trap Statements?
- How Trap Statements Work: Syntax & Basics
- Common Use Cases for Trap Statements
- Advanced Trap Techniques
- Best Practices for Using Traps
- Conclusion
- References
What Are Trap Statements?
A trap statement is a shell built-in that associates a custom action (e.g., a command, function, or script snippet) with a signal or error condition. When the shell receives the specified signal or encounters the error, it pauses execution and runs the action before continuing (or exiting).
Key Signals and Conditions
Traps can respond to:
- Signals: System messages like
SIGINT(user interrupt,Ctrl+C),SIGTERM(termination request), orSIGHUP(terminal hangup). - Special Conditions: Like
EXIT(triggered when the shell exits, regardless of exit status) orERR(triggered when a command exits with a non-zero status, in Bash).
Common signals you’ll use with traps:
| Signal | Name | Description |
|---|---|---|
EXIT | Exit | Triggered when the shell script exits. |
ERR | Error | Triggered on command failure (Bash-specific). |
SIGINT | Interrupt | Triggered by Ctrl+C (user interrupt). |
SIGTERM | Termination | Triggered by kill (graceful termination). |
How Trap Statements Work: Syntax & Basics
The basic syntax for a trap statement is:
trap 'command_or_action' SIGNAL [SIGNAL2 ...]
'command_or_action': The code to run when the signal/condition is triggered (enclose in quotes to handle spaces/newlines).SIGNAL: The signal or condition to trap (e.g.,EXIT,ERR,SIGINT).
Example 1: Simple Trap to Print a Message
Let’s start with a trivial example: a trap that prints a message when the script exits (using the EXIT signal):
#!/bin/bash
# Define a trap for EXIT
trap 'echo "Script exited successfully!"' EXIT
echo "Doing some work..."
sleep 2 # Simulate work
When you run this script:
- It prints “Doing some work…“.
- Sleeps for 2 seconds.
- Exits, triggering the
EXITtrap, which prints “Script exited successfully!“.
Example 2: Trapping Multiple Signals
You can trap multiple signals in one line. For example, trap SIGINT (Ctrl+C) and SIGTERM (kill) to print a message:
#!/bin/bash
# Trap SIGINT (Ctrl+C) and SIGTERM (kill)
trap 'echo "Received interrupt! Exiting..."' SIGINT SIGTERM
echo "Running (press Ctrl+C to interrupt)..."
while true; do sleep 1; done # Infinite loop
Now, if you press Ctrl+C or run kill <script_pid>, the script will print “Received interrupt! Exiting…” before terminating.
Common Use Cases for Trap Statements
Let’s dive into practical scenarios where traps shine.
3.1 Error Handling with the ERR Signal
The ERR signal (Bash-specific) triggers when a command exits with a non-zero status (i.e., fails). This is critical for catching errors early and avoiding silent failures.
How It Works:
By default, ERR does not trigger for:
- Commands in conditionals (e.g.,
if command; then ...). - Commands in pipelines (e.g.,
cmd1 | cmd2—useset -o pipefailto override this). - Commands with exit status inverted by
!(e.g.,! failing_cmd).
Example: Exit on Error with a Message
Use ERR to force the script to exit and print an error message when any command fails:
#!/bin/bash
# Trap ERR: print error details and exit
trap 'echo "Error occurred at line $LINENO: $BASH_COMMAND" >&2; exit 1' ERR
echo "Running command 1 (success)..."
ls -l # This works
echo "Running command 2 (failure)..."
ls -l /non/existent/path # This fails (non-zero exit)
echo "This line will NOT run (script exits on error)..."
Output:
Running command 1 (success)...
total 8
-rwxr-xr-x 1 user user 234 Oct 5 10:00 script.sh
Running command 2 (failure)...
ls: cannot access '/non/existent/path': No such file or directory
Error occurred at line 9: ls -l /non/existent/path
Here, $LINENO gives the line number of the failed command, and $BASH_COMMAND shows the command itself—critical for debugging!
3.2 Cleanup on Exit (EXIT Signal)
One of the most valuable uses of traps is cleaning up resources (temporary files, locks, network connections) when a script exits—even if it exits due to an error or user interrupt.
Example: Delete Temporary Files on Exit
Suppose your script creates a temporary directory for processing. Use EXIT to ensure the directory is deleted, no matter how the script exits (success, error, or Ctrl+C):
#!/bin/bash
# Create a temporary directory
TMP_DIR=$(mktemp -d -t myscript-XXXXXX)
echo "Using temporary directory: $TMP_DIR"
# Trap EXIT: delete the temp dir on exit
trap 'rm -rf "$TMP_DIR"; echo "Cleaned up temp dir: $TMP_DIR"' EXIT
# Simulate work: create a file in the temp dir
echo "Hello, temp!" > "$TMP_DIR/data.txt"
# Simulate an error (uncomment to test cleanup on failure)
# ls /non/existent/path
echo "Work done. Exiting normally..."
Test It:
- Run the script normally: The temp dir is deleted on exit.
- Press
Ctrl+Cmid-execution: The trap still runs, deleting the dir. - Uncomment the failing
lscommand: The script errors out, but the trap still cleans up.
3.3 Handling User Interrupts (SIGINT/SIGTERM)
Users often interrupt scripts with Ctrl+C (SIGINT) or terminate them with kill (SIGTERM). Without traps, this can leave partial files, locks, or unclosed resources.
Example: Clean Up Partial Downloads on Interrupt
Suppose your script downloads a large file. Use SIGINT/SIGTERM traps to delete the partial file if the user interrupts:
#!/bin/bash
FILE="large_file.iso"
PARTIAL_FILE="${FILE}.part"
# Trap SIGINT and SIGTERM: delete partial file and exit
trap 'rm -f "$PARTIAL_FILE"; echo "Download interrupted. Partial file deleted."; exit 1' SIGINT SIGTERM
echo "Downloading $FILE..."
curl -o "$PARTIAL_FILE" "https://example.com/$FILE" # Simulate download
# If download succeeds, rename partial file
mv "$PARTIAL_FILE" "$FILE"
echo "Download complete: $FILE"
Test It:
- Start the script, then press
Ctrl+Cmid-download. The partial file (large_file.iso.part) will be deleted.
3.4 Logging and Debugging
Traps can log errors, environment variables, or stack traces to help debug failed scripts. Combine ERR with logging to capture context about failures.
Example: Log Errors to a File
#!/bin/bash
LOG_FILE="script_errors.log"
# Trap ERR: log error details to a file and exit
trap 'echo "[$(date +%Y-%m-%dT%H:%M:%S)] Error at line $LINENO: $BASH_COMMAND (Exit code: $?)" >> "$LOG_FILE"; exit 1' ERR
echo "Doing work..."
invalid_command # This will fail (trigger ERR trap)
echo "This line won't run..."
After running, script_errors.log will contain:
[2023-10-05T14:30:00] Error at line 8: invalid_command (Exit code: 127)
Advanced Trap Techniques
Once you master the basics, these advanced patterns will make your scripts even more robust.
4.1 Nested Traps: Overriding and Restoring Traps
You can temporarily override a trap, then restore the original behavior later. This is useful for functions that need custom error handling.
Example: Save and Restore a Trap
#!/bin/bash
# Original EXIT trap
trap 'echo "Original cleanup: Exit"' EXIT
# Function with a temporary EXIT trap
special_task() {
echo "Running special task..."
# Save original EXIT trap
original_trap=$(trap -p EXIT)
# Override EXIT trap for this function
trap 'echo "Special cleanup: Task done"' EXIT
sleep 2 # Simulate work
# Restore original EXIT trap
eval "$original_trap"
}
special_task
echo "Back to main script..."
Output:
Running special task...
Special cleanup: Task done # From temporary trap
Back to main script...
Original cleanup: Exit # From restored original trap
4.2 Conditional Traps
Use conditionals inside traps to handle different scenarios (e.g., only clean up if a resource exists).
Example: Conditional Cleanup
Only delete a lock file if it exists:
#!/bin/bash
LOCK_FILE="/tmp/myscript.lock"
# Trap EXIT: delete lock file only if it exists
trap 'if [ -f "$LOCK_FILE" ]; then rm -f "$LOCK_FILE"; echo "Lock file removed."; fi' EXIT
# Create lock file
touch "$LOCK_FILE"
echo "Lock acquired: $LOCK_FILE"
# Simulate work (uncomment to test without lock file)
# rm -f "$LOCK_FILE"
echo "Doing work..."
sleep 2
4.3 Combining Traps with set Options
For stricter error handling, combine traps with set options like:
set -e: Exit the script if any command fails (complementsERRtraps).set -u: Treat undefined variables as errors (triggersERR).set -o pipefail: Make pipelines fail if any command in the pipeline fails (triggersERR).
Example: Strict Error Handling
#!/bin/bash
# Strict error settings
set -euo pipefail
# Trap ERR: print debug info and exit
trap 'echo "Error at line $LINENO: $BASH_COMMAND (Exit code: $?)" >&2; exit 1' ERR
# Undefined variable (triggers set -u)
echo "Hello, $UNDEFINED_VAR" # Fails: undefined variable
# Pipeline with a failing command (triggers pipefail)
# ls /non/existent | grep "file" # Fails: ls exits non-zero
Best Practices for Using Traps
To avoid pitfalls, follow these guidelines:
-
Set Traps Early: Define traps near the start of your script, before critical operations (e.g., creating temp files, acquiring locks).
-
Use Functions for Complex Actions: For long trap logic, define a function and call it in the trap:
cleanup() { rm -rf "$TMP_DIR" echo "Cleanup complete." } trap cleanup EXIT -
Test Trap Behavior: Verify traps work for success, error, and interrupt scenarios.
-
Avoid Over-Trapping: Too many traps can make scripts hard to debug. Focus on critical signals (
EXIT,ERR,SIGINT,SIGTERM). -
Quote Variables in Traps: Use double quotes for variables in traps to ensure they expand correctly (e.g.,
trap "rm -rf '$TMP_DIR'" EXIT). -
Document Traps: Add comments explaining what each trap does (e.g.,
# Trap EXIT to clean up temp files).
Conclusion
Trap statements are a cornerstone of robust shell scripting. By mastering them, you can ensure your scripts handle errors gracefully, clean up resources, and provide meaningful feedback—even when things go wrong. Start with simple use cases like cleanup on exit or error handling, then layer in advanced techniques like conditional traps or strict set options.
With traps, you’ll move from writing fragile scripts to building reliable, production-ready tools.