SoFunction
Updated on 2025-03-04

Linux shell uses trap command to handle program interrupts elegantly

See a common scenario

Suppose you are developing a data backup script. This script needs to perform the following operations:

  • Create a temporary working directory
  • Copy the data to a temporary directory
  • Compression packaging
  • Clean up temporary files
#!/bin/bash

WORK_DIR="/tmp/backup_$(date +%Y%m%d)"

echo "Start backup..."
mkdir -p "$WORK_DIR"
echo "Create a temporary directory: $WORK_DIR"

echo "Copy the file..."
cp -r /path/to/data "$WORK_DIR/"
sleep 5  # Simulation time-consuming operation
echo "Compression packaging..."
tar -czf  "$WORK_DIR"
sleep 3  # Simulation time-consuming operation
echo "Clean up temporary files..."
rm -rf "$WORK_DIR"

echo "Backup is complete!"

What if I interrupt the script!

When we run this script, what happens if we press Ctrl+C to interrupt the operation during execution?

Temporary Directory$WORK_DIRWill be left in the system because the cleaning step is not performed. Over time, these uncleaned temporary files will occupy a lot of disk space.

Improve programs with trap command

At this time,trapThe order comes in handy.trapA specific signal can be captured and corresponding processing functions can be performed. SIGINT (usually triggered by Ctrl+C) is one of the most common signals.

First, we define an interrupt handling function:

on_interrupt() {
    echo -e "\nThe program was interrupted!"
    echo "Clean temporary files..."
    rm -rf "$WORK_DIR"
    exit 1
}

Then, use it at the beginning of the scripttrapSet up signal processing:

trap on_interrupt SIGINT

The complete improved script is as follows:

#!/bin/bash

WORK_DIR="/tmp/backup_$(date +%Y%m%d)"

# Define interrupt handling functionon_interrupt() {
    echo -e "\nThe program was interrupted!"
    echo "Clean temporary files..."
    rm -rf "$WORK_DIR"
    exit 1
}

# Setting up traptrap on_interrupt SIGINT

echo "Start backup..."
mkdir -p "$WORK_DIR"
echo "Create temporary directory: $WORK_DIR"

echo "Copy the file..."
cp -r /path/to/data "$WORK_DIR/"
sleep 5  # Simulation time-consuming operation
echo "Compression Packaging..."
tar -czf  "$WORK_DIR"
sleep 3  # Simulation time-consuming operation
echo "Clean temporary files..."
rm -rf "$WORK_DIR"

echo "Backup is complete!"

trap command description

trapThe basic syntax of a command is:

trap command signal

in:

  • commandCan be a function name or a direct command
  • signalIt is the signal name to be captured, such as SIGINT, SIGTERM, etc.

Common signals include:

  • SIGINT (2): User presses Ctrl+C
  • SIGTERM (15): Termination signal
  • EXIT: When the script exits

You can also capture multiple signals at the same time:

trap on_interrupt SIGINT SIGTERM

By usingtrapCommands andon_interruptFunction, we implement:

  • Handle program interrupts gracefully
  • Ensure temporary resources are properly cleaned
  • Provide friendly user tips

This pattern is not only suitable for backup scripts, but also in any script that requires resource cleaning, such as:

  • Temporary file processing
  • Database connection cleaning
  • Lock file deletion
  • Process cleaning

Extensions: Advanced applications of trap commands

Multi-signal processing

Sometimes we need to process different signals differently. For example, in a data processing script:

#!/bin/bash

# Define variablesDATA_FILE=""
TEMP_FILE=""
LOG_FILE=""

# Process Ctrl+Con_interrupt() {
    echo -e "\nReceived SIGINT, is closing gracefully..."
    cleanup
    exit 1
}

# Handle SIGTERMon_terminate() {
    echo -e "\nReceived SIGTERM, save the progress and exit..."
    save_progress
    cleanup
    exit 1
}

# Process normal exiton_exit() {
    echo "The program ends normally, clean up..."
    cleanup
}

# Clean up functionscleanup() {
    rm -f "$TEMP_FILE"
    echo "Cleaning Completed"
}

# Save progresssave_progress() {
    echo "Save the current progress to $LOG_FILE"
    echo "Progress saved at $(date)" >> "$LOG_FILE"
}

# Set up multiple signal processingtrap on_interrupt SIGINT
trap on_terminate SIGTERM
trap on_exit EXIT

# Main Programecho "Start the data..."
while true; do
    echo "Processing..."
    sleep 1
done

Temporarily disable and restore signal processing

Sometimes we need to temporarily disable signal processing, such as when performing critical operations:

#!/bin/bash

critical_operation() {
    # Temporarily disable Ctrl+C    trap '' SIGINT
    
    echo "Perform a critical operation, pressing Ctrl+C during this period is invalid..."
    sleep 5
    
    # Recover signal processing    trap on_interrupt SIGINT
    echo "Critical operation is completed, normal signal processing is restored"
}

on_interrupt() {
    echo -e "\nThe operation was interrupted!"
    exit 1
}

trap on_interrupt SIGINT

echo "Start execution..."
critical_operation
echo "Continue other operations..."

DEBUG signal and debug processing

DEBUG is not an interrupt signal, but a special trap event of Bash. It is triggered before each command is executed and is mainly used for debugging purposes. Let's look at a more practical example:

#!/bin/bash

# Used to control whether the DEBUG trap is triggered in the error handling functionIN_ERROR_HANDLER=0

# Define debug processing functionson_debug() {
    # If in the error handling function, skip debug output    if ((IN_ERROR_HANDLER)); then
        return
    fi
    # $1 is the line number, $BASH_COMMAND is the command to be executed    echo "[DEBUG] Line $1: Prepare to execute -> $BASH_COMMAND"
}

# Error handling functionon_error() {
    local err=$?  # Save the error code immediately    local line=$1
    local cmd=$2
    
    # Set flags to prevent DEBUG trap from triggering in error handling    IN_ERROR_HANDLER=1
    
    echo "[ERROR] line $line failed to execute"
    echo "Command: $cmd"
    echo "Error code: $err"
    
    # Reset flag    IN_ERROR_HANDLER=0
}

# Enable debug trackingenable_debug() {
    # Enable ERR trap    set -E
    # -T option can display function call trace    set -T
    # Set DEBUG trap and pass in line number parameters    trap 'on_debug ${LINENO}' DEBUG
    trap 'on_error ${LINENO} "$BASH_COMMAND"' ERR
}

# Turn off debug trackingdisable_debug() {
    trap - DEBUG
    trap - ERR
    set +E
    set +T
}

# Control whether to enable debugging through environment variablesif [[ "${ENABLE_DEBUG}" == "true" ]]; then
    enable_debug
fi

# Test functiontest_function() {
    echo "Execute test function"
    local result=$((2 + 2))
    echo "Calculation result: $result"
    # Deliberately create an error    ls /nonexistent_directory
}

# Main Programecho "Start execution..."
test_function
echo "Try to access a file that does not exist..."
cat nonexistent_file.txt

How to use:

# Normal execution./

# Turn on debug mode executionENABLE_DEBUG=true ./

Normal mode output:

Start executing...
Execute test functions
Calculation result: 4
ls: cannot access '/nonexistent_directory': No such file or directory
Try to access a file that does not exist...
cat: nonexistent_file.txt: No such file or directory

DEBUG mode output:

[DEBUG] Line 41: Prepare to execute -> trap 'on_error ${LINENO} "$BASH_COMMAND"' ERR
[DEBUG] Line 67: Prepare to execute -> echo "Start execution..."
Start executing...
[DEBUG] Line 68: Prepare to execute -> test_function
[DEBUG] Line 58: Prepare to execute -> test_function
[DEBUG] Line 59: Preparing to execute -> echo "Execute test function"
Execute test functions
[DEBUG] Line 60: Prepare to execute -> local result=$((2 + 2))
[DEBUG] Line 61: Prepare to execute -> echo "Calculation result: $result"
Calculation result: 4
[DEBUG] Line 63: Prepare to execute -> ls /noneexistent_directory
ls: cannot access '/nonexistent_directory': No such file or directory
[DEBUG] Line 63: Prepare to execute -> ls /noneexistent_directory
[DEBUG] Line 17: Prepare to execute -> ls /noneexistent_directory
[DEBUG] Line 18: Prepare to execute -> local err=$?
[DEBUG] Line 19: Prepare to execute -> local line=$1
[DEBUG] Line 20: Prepare to execute -> local cmd=$2
[DEBUG] Line 23: Prepare to execute -> IN_ERROR_HANDLER=1
[ERROR] Line 63 Execution failed
Command: ls /noneexistent_directory
Error code: 2
[DEBUG] Line 68: Prepare to execute -> ls /noneexistent_directory
[DEBUG] Line 17: Prepare to execute -> ls /noneexistent_directory
[DEBUG] Line 18: Prepare to execute -> local err=$?
[DEBUG] Line 19: Prepare to execute -> local line=$1
[DEBUG] Line 20: Prepare to execute -> local cmd=$2
[DEBUG] Line 23: Prepare to execute -> IN_ERROR_HANDLER=1
[ERROR] Line 68 Execution failed
Command: ls /noneexistent_directory
Error code: 2
[DEBUG] Line 69: Prepare to execute -> echo "Try to access a non-existent file..."
Try to access a file that does not exist...
[DEBUG] Line 70: Prepare to execute -> cat nonexistent_file.txt
cat: nonexistent_file.txt: No such file or directory
[DEBUG] Line 70: Prepare to execute -> cat nonexistent_file.txt
[DEBUG] Line 17: Prepare to execute -> cat nonexistent_file.txt
[DEBUG] Line 18: Prepare to execute -> local err=$?
[DEBUG] Line 19: Prepare to execute -> local line=$1
[DEBUG] Line 20: Prepare to execute -> local cmd=$2
[DEBUG] Line 23: Prepare to execute -> IN_ERROR_HANDLER=1
[ERROR] Line 70 Execution failed
Command: cat nonexistent_file.txt
Error code: 1

File lock mechanism trap vs flock

Let's compare the lock mechanisms of trap and flock:

File locks using trap

#!/bin/bash

LOCK_FILE="/tmp/"
PID_FILE="/tmp/"

cleanup() {
    rm -f "$LOCK_FILE" "$PID_FILE"
    echo "Clean lock files and PID files"
}

get_lock() {
    if [ -e "$LOCK_FILE" ]; then
        local pid
        pid=$(cat "$PID_FILE" 2>/dev/null)
        if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
            echo "Another example(PID: $pid)Running"
            exit 1
        fi
        # If the process does not exist, clean up the old lock        cleanup
    fi
    
    echo $$ > "$PID_FILE"
    touch "$LOCK_FILE"
    trap cleanup EXIT
}

Implementation using flock:

#!/bin/bash

LOCK_FILE="/tmp/"

(
    # Get the file lock and wait for up to 5 seconds    flock -w 5 200 || { echo "Cannot acquire lock, another instance is running"; exit 1; }
    
    echo "Get the lock, start executing..."
    sleep 10
    echo "Execution Completed"
    
) 200>"$LOCK_FILE"

Comparative Analysis

  • reliability

    • flock is more reliable, it uses kernel-level file locks
    • The trap method may leave orphaned lock files in extreme cases (such as system crashes)
  • Use scenarios

    • flock is suitable for strict production environments
    • The trap method is suitable for simple scripting and development environments
  • Recommended choice

    • Flock is recommended because it:
      • Automatic process termination
      • Support timeout settings
      • Provides blocking and non-blocking modes
      • More reliable

Transaction implementation

#!/bin/bash

# Status variablesTRANSACTION_ACTIVE=false

# Dynamically change signal processingupdate_signal_handler() {
    if $TRANSACTION_ACTIVE; then
        # Transaction is in progress, set interrupt processing to prompt and end        trap 'echo "The transaction is in progress and has been forcibly interrupted..."; cleanup; exit 1' SIGINT
    else
        # Non-transaction state, can exit safely        trap 'echo "Exit normally..."; exit 0' SIGINT
    fi
}

# Clean up functionscleanup() {
    echo "Perform a cleanup operation..."
    # Add the actual cleaning code here}

# Simulate transactionsstart_transaction() {
    TRANSACTION_ACTIVE=true
    update_signal_handler
    echo "Transaction Begins"
    
    # Simulate transaction operations    echo "Execute transaction steps 1/3"
    sleep 2
    echo "Execute transaction step 2/3"
    sleep 2
    echo "Execute transaction step 3/3"
    sleep 2
    
    TRANSACTION_ACTIVE=false
    update_signal_handler
    echo "Transaction Completed"
}

# Set initial signal processingupdate_signal_handler

# Main program execution processecho "Start execution..."
start_transaction
echo "Continue other operations..."

Execution process description:

  • Script Start

    • TRANSACTION_ACTIVEThe initial value isfalse
    • First callupdate_signal_handler, set normal interrupt processing
  • implementstart_transaction

    • set upTRANSACTION_ACTIVEfortrue
    • Update signal processing to transaction protection mode
    • Perform transaction operations
    • When finished, set upTRANSACTION_ACTIVEforfalse
    • Restore normal signal processing
  • Signal processing behavior

    • SIGINT is received during the transaction: display an interrupt message, perform a cleanup, and then exit
    • Non-transaction state received SIGINT: Direct secure exit

With these advanced usages, we can build more robust and reliable shell scripts. Whether it is handling unexpected interrupts, implementing lock mechanisms, or performing debugging,trapThey are all powerful tools.

The above is the detailed content of Linux shell using trap commands to handle program interrupts elegantly. For more information about shell trap handler interrupts, please pay attention to my other related articles!