1. Overview

In the Linux command-line ecosystem, mastering the trap command can be essential for crafting robust and resilient shell scripts. Specifically, it facilitates the interception and management of signals, ensuring scripts gracefully terminate and maintain stability.

In this tutorial, we delve into the functionality of the trap command, providing examples and explanations to assist in harnessing its power effectively. Additionally, we offer insights into several use cases.

2. Understanding the trap Command

Essentially, the trap command enables shell scripts to respond to signals originating from system or user activity. Consequently, such scripts enhance their reliability and improve the user experience executing specific actions or cleanup tasks for particular signals.

2.1. Options and Common Signals

The trap command in Bash offers two options:

  • -p: shows commands for signals
  • -l: prints a list of all the signals and their numbers

Notably, the trap -l command doesn’t display signals 32 and 33 in the output because they aren’t supported on Linux.

2.2. Syntax

The trap command has a fairly simple syntax:

trap <action> <condition...>

Here, action* is a script or command to run, while condition is usually a list of signals that trigger the *action. If action is an integer, it’s also treated as a condition.

This way, we can indicate a signal in the condition section, with action often being a predefined function or a small snippet.

2.3. Basic Example With SIGINT

The fundamental syntax of trap establishes a foundation to effectively handle signals in shell scripting.

To demonstrate, let’s see an example script to manage the SIGINT signal, sent to the command running in the foreground upon pressing Ctrl+C in most environments:

$ cat trap_example.sh
#!/bin/bash

trap "echo Exiting; exit" SIGINT
while true do
    echo Test
    sleep 1
done

Here, we start with a #!/bin/bash shebang, which indicates we’re dealing with a Bash script. The first line of the script uses the trap command, which waits for the SIGINT signal, prints a message, and uses exit to continue the interrupt.

Then, a while loop executes infinitely, printing Test and sleeping for 1 second on each iteration. Thus, we see a very basic handling of a signal.

2.4. Example With the EXIT Condition

When combined with trap, the EXIT condition offers a robust mechanism for controlling script termination:

$ cat trap_exit.sh
#!/bin/bash

# set up trap to catch exit attempt
trap "echo Exiting..." EXIT

In this example, we utilize the trap command with the EXIT condition and a basic echo as the action. Notably, even though we don’t have an explicit exit command at the end of the script, it terminates upon completion and the trap still activates

3. trap Use Cases

The trap command is crucial to managing signaling in shell scripts, enabling them to gracefully handle interruptions and regular signals.

3.1. Safety Net

Let’s see an example that uses a function instead of a single command when trapping SIGINT:

$ cat trap_example.sh
#!/bin/bash

# Cleanup function
cleanup() {
    echo "Cleanup: Interrupt signal received. Exiting..."
    # Cleanup actions go here
    exit 1
}

# Set up a trap to catch SIGINT (Ctrl+C) and execute the cleanup function
trap cleanup SIGINT

# Main script logic
echo "Script started. Press Ctrl+C to trigger cleanup."

# Simulate a long-running process
sleep 10

# End of script
echo "Script completed."

Here, the script defines a cleanup() function. This function echoes a message to indicate it ran, handles any necessary cleanup, and exits with a status of 1.

Using the trap command, the script further sets up the handling of SIGINT, executing the cleanup() function upon receiving the signal. Next, there’s a prompt for the user, while a simulated long process is running via sleep.

Overall, this showcases the trap command as a way of handling SIGINT, ensuring cleanup before termination. This way, we can run tasks or even prevent an interrupt altogether.

3.2. Temporary Files

For example, the trap command is frequently used to clean up temporary files if the script is interrupted. So, we can augment the cleanup() function to remove specific files and only then continue with the script termination:

$ cat trap_tempdel.sh
#!/bin/bash
TMPFILE=$(mktemp)

trap cleanup 1 2 3 6

cleanup()
{
    echo "Removing temporary file..."
    rm -rf "$TMPFILE"
    exit
}

sleep 10

In the example, the trap executes the cleanup function upon detecting one of four signals:

  • SIGHUP (1)
  • SIGINT (2)
  • SIGQUIT (3)
  • SIGABRT (6)

Notably, these signals are referenced by their numbers.

3.3. Disable Signal Handling

Consequently, trap can also prevent some user interruptions of script execution, safeguarding sensitive commands from causing damage.

Let’s see the syntax for disabling a signal:

trap "" <signal>

Here, since we use an empty string, we perform no action upon handling signal.

For example, we can trap the SIGINT and SIGABRT signals without processing them:

trap "" SIGINT SIGABRT

To re-enable the default processing of signals at any time during the script, we reset the rules using the  dash symbol:

trap - SIGINT SIGABRT

This way, we can temporarily prevent a signal from being processed.

3.4. Using $?

The $? variable retains the exit status of the last command executed in a shell script. Let’s integrate it with trap in a concise example:

$ cat trap_.sh
#!/bin/bash

trap "false" SIGINT

echo "Script started. Press Ctrl+C to trigger status."

sleep 10

echo $?

In this case, we set $? to 1 via false in the SIGINT trap signal handler. After that, we output $? with echo to verify its value is 0 if no interruptions occur, but changes to 1 if we use Ctrl+C.

3.5. Saving trap

In certain scenarios, it can be beneficial to preserve and restore traps to maintain consistent signal handling throughout script execution:

$ cat trap_preserving.sh
#!/bin/bash

# Save existing trap for SIGINT
trap_saved_SIGINT=$(trap -p SIGINT)

# Set up trap to catch SIGINT (Ctrl+C) and execute cleanup function
trap "echo Trapped." SIGINT

sleep 10

eval "$trap_saved_SIGINT"

The script first saves the current trap for SIGINT using trap -p SIGINT, storing it as trap_saved_SIGINT. This line employs command substitution to get the desired result. The script then sets up a new trap for SIGINT to activate the cleanup function. After executing the main script tasks, it restores the original trap for SIGINT, ensuring consistent signal handling.

4. Conclusion

In this article, we’ve emphasized the importance of the trap command in handling signals and ensuring script reliability. By understanding its syntax, we can create resilient scripts capable of gracefully handling interruptions and termination requests.

In particular, we saw several use cases in specific scenarios.