bash: Why can't I set a trap for SIGINT in a background shell?

1.4k views Asked by At

Here's a simple program that registers two trap handlers and then displays them with trap -p. Then it does the same thing, but in a child background process.

Why does the background process ignore the SIGINT trap?

#!/bin/bash

echo "Traps on startup:"
trap -p
echo ""

trap 'echo "Received INT"' INT
trap 'echo "Received TERM"' TERM

echo "Traps set on parent:"
trap -p
echo ""

(
    echo "Child traps on startup:"
    trap -p
    echo ""

    trap 'echo "Child received INT"' INT
    trap 'echo "Child received TERM"' TERM

    echo "Traps set on child:"
    trap -p
    echo ""
) &

child_pid=$!
wait $child_pid

Output:

$ ./show-traps.sh
Traps on startup:

Traps set on parent:
trap -- 'echo "Received INT"' SIGINT
trap -- 'echo "Received TERM"' SIGTERM

Child traps on startup:

Traps set on child:
trap -- 'echo "Child received TERM"' SIGTERM
2

There are 2 answers

4
Petr Skocik On BEST ANSWER

SIGINT and SIGQUIT are ignored in backgrounded processes (unless they're backgrounded with set -m on). It's a (weird) POSIX requirement (see 2. Shell Command Language or my SO question Why do shells ignore SIGINT and SIGQUIT in backgrounded processes? for more details).

Additionally, POSIX requires that:

When a subshell is entered, traps that are not being ignored shall be set to the default actions, except in the case of a command substitution containing only a single trap command ..

However, even if you set the INT handler in the subshell again after it was reset, the subshell won't be able to receive it because it's ignored (you can try it or you can inspect the signal ignore mask using ps, for example).

0
x-yuri On

Background jobs are not supposed to be tied to the shell that started them. If you exit a shell, they will continue running. As such they shouldn't be interrupted by SIGINT, not by default. When job control is enabled, that is fulfilled automatically, since background jobs are running in separate process groups. When job control is disabled (generally in non-interactive shells), bash makes the asynchronous commands ignore SIGINT.

The relevant parts of the documentation:

Non-builtin commands started by Bash have signal handlers set to the values inherited by the shell from its parent. When job control is not in effect, asynchronous commands ignore SIGINT and SIGQUIT in addition to these inherited handlers. Commands run as a result of command substitution ignore the keyboard-generated job control signals SIGTTIN, SIGTTOU, and SIGTSTP.

https://www.gnu.org/software/bash/manual/html_node/Signals.html

To facilitate the implementation of the user interface to job control, the operating system maintains the notion of a current terminal process group ID. Members of this process group (processes whose process group ID is equal to the current terminal process group ID) receive keyboard-generated signals such as SIGINT. These processes are said to be in the foreground. Background processes are those whose process group ID differs from the terminal’s; such processes are immune to keyboard-generated signals. Only foreground processes are allowed to read from or, if the user so specifies with stty tostop, write to the terminal. Background processes which attempt to read from (write to when stty tostop is in effect) the terminal are sent a SIGTTIN (SIGTTOU) signal by the kernel’s terminal driver, which, unless caught, suspends the process.

https://www.gnu.org/software/bash/manual/html_node/Job-Control-Basics.html

More on it here.