Bash – Strange problem with trap and SIGINT

background-processbashshellsignalstrap:

Please explain this:

#!/bin/bash
# This is scripta.sh
./scriptb.sh &
pid=$!
echo $pid started
sleep 3
while true
do
    kill -SIGINT $pid
    echo scripta.sh $$
    sleep 3
done 

#!/bin/bash
# This is scriptb.sh
trap "echo Ouch;" SIGINT

while true
do
 echo scriptb.sh $$
 sleep 1
done

When I run ./scripta.sh the trap doesn't print. If I switch from SIGINT to any other signal (I tried SIGTERM, SIGUSR1) the trap prints "Ouch" as expected. How can this happen?

Best Answer

If I switch from SIGINT to any other signal (I tried SIGTERM, SIGUSR1) the trap prints “Ouch” as expected.

Apparently you didn’t try SIGQUIT; you will probably find that it behaves the same as SIGINT.

The problem is job control.

In the early days of Unix, whenever the shell put a process or pipeline into the background, it set those processes to ignore SIGINT and SIGQUIT, so they would not be terminated if the user subsequently typed Ctrl+C (interrupt) or Ctrl+\ (quit) to a foreground task.  When job control came along, it brought process groups along with it, and so now all the shell needs to do is put the background job into a new process group; as long as that is not the current terminal process group, the processes will not see signals coming from the keyboard (Ctrl+C, Ctrl+\ and Ctrl+Z (SIGTSTP)).  The shell can leave background processes with the default signal disposition.  In fact, it probably has to, for the processes to be killable by Ctrl+C when they are brought into the foreground.

But non-interactive shells don’t use job control.  It makes sense that a non-interactive shell would fall back to the old behavior of ignoring SIGINT and SIGQUIT for background processes, for the historical reason — to allow the background processes to continue to run, even if keyboard-type signals are sent to them.  And shell scripts run in non-interactive shells.

And, if you look at the last paragraph under the trap command in bash(1), you’ll see

Signals ignored upon entry to the shell cannot be trapped or reset.

So, if you run ./scriptb.sh & from your interactive shell’s command prompt, its signal dispositions are left alone (even though it’s being put into the background), and the trap command works as expected.  But, if you run ./scripta.sh (with or without &), it runs the script in a non-interactive shell.  And when that non-interactive shell runs ./scriptb.sh &, it sets the scriptb process to ignore interrupt and quit.  And therefore the trap command in scriptb.sh silently fails.

Related Question