8

I have a Bash script that runs a long running process in the foreground. When it receives a SIGQUIT signal, it should perform various cleanup operations such as killing itself and all of its child processes (via kill of process group etc.). A minimal script, that should catch the signal, is shown below (called test_trap.sh):

#!/bin/bash

trap 'echo "TRAP CAUGHT"; exit 1' QUIT  # other required signals are omitted for brevity

echo starting sleep
sleep 11666
echo ending sleep

echo done

I would like to send the SIGHUP signal to the process of the test_trap.sh script. However, sending a SIGHUP to the test_trap.sh does not trigger the trap expression, but only when I send the signal to the child sleep 11666 process does the trap fire. Below is a bash session demonstrating this:

bash-4.1$ test_trap.sh &
[1] 19633
bash-4.1$ starting sleep

bash-4.1$ kill -s SIGQUIT 19633
bash-4.1$ jobs
[1]+  Running                 test_trap.sh &
bash-4.1$ ps -ef --forest --cols=10000 | grep '11666\|test_trap.sh' | grep -v grep
theuser   19633 12227  0 07:40 pts/4    00:00:00              \_ /bin/bash ./test_trap.sh
theuser   19634 19633  0 07:40 pts/4    00:00:00              |   \_ sleep 11666
bash-4.1$ kill -s SIGQUIT 19634
bash-4.1$ Quit (core dumped)
TRAP CAUGHT

[1]+  Exit 1                  test_trap.sh
bash-4.1$ ps -ef --forest --cols=10000 | grep '11666\|test_trap.sh' | grep -v grep
bash-4.1$ 

Note that the "sleep 11666" is just a representative process. That process can actually be an interactive subshell (e.g., bash -i).

Why doesn't the parent test_trap.sh process catch the SIGHUP signal? Why would the trap fire only when the process for sleep 11666 was signaled?

I do not want to use uncatchable SIGKILL as I do need to do an assortment of cleanup operations in the trap expression.

This script is intended run on any fairly recent version of any Linux distribution containing Bash (e.g., not Cygwin).

References:

  1. killing Parent process along with child process using SIGKILL
  2. Kill bash and child process
Community
  • 1
  • 1
bgoodr
  • 2,744
  • 1
  • 30
  • 51

3 Answers3

10

bash must wait for sleep to complete before it can execute the handler. A good workaround is to run sleep in the background, then immediately wait for it. While sleep is uninterruptible, wait is not.

trap 'kill $sleep_pid; echo "TRAP CAUGHT"; exit 1' QUIT

echo starting sleep
sleep 11666 &
sleep_pid=$!
wait
echo ending sleep

echo done

The recording of sleep_pid and using it to kill sleep from the handler are optional.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • 2
    I have to mark this as the answer since you asked my question and it was quite helpful. However, I said "That process can actually be an interactive subshell (e.g., bash -i). " and I should have added "I don't want to put the representative process in the background because it could be an interactive shell". – bgoodr Oct 20 '15 at 16:22
  • Keep in mind that the user of the interactive subshell could set their own traps anyway to avoid the shell being interrupted by the caller. – chepner Oct 20 '15 at 16:48
  • @bgoodr did you ever find a solution that does not require putting the process in the background? – trex005 Oct 30 '16 at 22:45
  • @trex005 No, unfortunately I did not. – bgoodr Nov 19 '16 at 15:59
2

Actually, bash is receiving the signal, but it is in an uninterruptible state waiting for the sleep command to end. When it ends, bash will react to the signal and execute the trap.

You can replace the long sleep command with a loop of short sleep commands:

while true
do
    sleep 1
done

With that, if you send the signal to the bash process, it will react as soon as the currently executing sleep command ends, that is, at most 1 second after it was sent.

Alvaro Gutierrez Perez
  • 3,669
  • 1
  • 16
  • 24
  • Thanks but see my response at http://stackoverflow.com/questions/33240221/how-to-use-trap-reliably-using-bash-running-foreground-child-processes#comment54286155_33241062 as given to @chepner's answer. If sleeping was all I wanted to do, then your answer would suffice, but that is just a representative process that I would ideally like to allow to be running in the foreground since it might end up being a interactive `bash -i` process. – bgoodr Oct 20 '15 at 16:27
0

Try with the signal SIGINT (the same which is sent by pressing Ctrl+C) instead of SIGKILL. Other signals only work when the bash can process I/O or some other condition.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820