1. Introduction
The Linux system uses signals, which we can regard as events triggered under specific conditions. In Bash scripts, we can listen to this signal to respond correctly to these circumstances.
In this tutorial, we’ll learn to handle the common SIGINT signal emitted when the user presses the Ctrl+C combination.
2. The trap Command
The trap built-in allows the execution of Bash commands when the signal arrives. Therefore, we can use it to customize the signal handling or ignore it altogether. We’re going to use the syntax:
trap [-lp] [action] [signal]
action is a string with Bash commands, which can be parsed and evaluated in the way that eval does. signal is a signal’s value or name. trap recognizes the signal’s short name, e.g., INT for SIGINT. Depending on the implementation, we can also use full names.
Finally, the -l option lists all signals, while -p prints the action attached to the given signal.
3. Handling SIGINT
Let’s write a simple script that intercepts the SIGINT signal and asks for confirmation:
#!/bin/bash
ctrlc_received=0
function handle_ctrlc()
{
echo
if [[ $ctrlc_received == 0 ]]
then
echo "I'm hmmm... running. Press Ctrl+C again to stop!"
ctrlc_received=1
else
echo "It's all over!"
exit
fi
}
# trapping the SIGINT signal
trap handle_ctrlc SIGINT
while true
do
echo "I'm sleeping"
sleep 15
done
Let’s play a bit with this simple program and then summarize the observations:
- We need to properly handle the signal on our own. So, we use the exit command after the second interruption is detected.
- SIGINT interrupts the current action.
- Bash doesn’t resume the interrupted operation. The control is passed to the next command in the script. In this case, it’s the while loop, which in turn starts a new sleep command.
4. Ignoring Signals
As we’ve learned, the interrupted command is not concluded. So, we can get into trouble if the interruption signal comes during some important actions, even if we’ve trapped it. The remedy is to disable signals before running the critical section of the code and enable them later on.
We can use an empty string “” as an action to disable the SIGINT signal. After the critical section is done, we should pass the – (minus) action to enable Ctrl+C again:
#!/bin/bash
trap "" SIGINT
#critical section of the code
trap - SIGINT
#less critical part
Note that the – action restores the default signal handling. We can’t bring back custom handlers in this way.
It is worth noting that we can’t disable or ignore SIGKILL and SIGTERM.
5. Chaining Signal Handlers
We can trigger multiple actions by one signal with the Bash syntax for chaining commands:
trap 'command1;command2;command3' SIGINT
Let’s assume that our script trap_INT_chain demands a separate handler for each operation, e.g., for log writing and cleanup:
#!/bin/bash
function log_INT()
{
echo
echo "Logging interruption"
}
function clean_on_INT()
{
echo
echo "Cleaning on interruption"
}
# trapping the SIGINT signal
trap 'log_INT;clean_on_INT;exit' INT
while true
do
echo "I'm sleeping"
sleep 15
done
Now, after the script termination, we’re going to obtain the following:
$ ./trap_INT_chain
I'm sleeping
^C
Logging interruption
Cleaning on interruption
6. More Chaining
When we use handlers in a bigger script, we shouldn’t drop the already added handlers. We can achieve it with the help of the -p option to trap, which shows the handler attached to the signal. Let’s check it in the terminal:
$ trap 'echo SIGINT received and ignored!' INT
$ trap -p INT
trap -- 'echo SIGINT received and ignored!' SIGINT
Subsequently, we can get the commands to be executed with cut:
$ trap -p INT | cut -f2 -d \'
echo SIGINT received and ignored!
Now let’s assume that a handler is set for the INT signal. So, we can use the trap -p output to paste the existing commands:
$ trap "$( trap -p INT | cut -f2 -d \' );newCommand" INT
Things get more complicated when we don’t know if any handler is already set. To avoid a leading semicolon, we need to discern the empty trap‘s output:
$ commands="$( trap -p INT | cut -f2 -d \' )"
$ trap "${commands}${commands:+;}newCommand" INT
The work is done by the ${A:+B} operator. It returns B if A is set or a null string if A is unset.
7. Conclusion
In this article, we used the SIGINT signal as an example to learn the trap command. First, we demonstrated the custom Ctrl+C handler. Next, we learned to disable this signal to protect critical parts of the code. Finally, we chained multiple commands and attached them to one signal.