Scattered in bash documentation lies the truth about SIGINT and SIGQUIT signals being mysteriously ignored and non-trappable in background processes.
This has puzzled many, and will probably continue to do so.
Traps
Here's my try to unveil the terrible truth.
Let this script separate all cases and enlighten us:
cat <<'EOF' > /tmp/test.sh #!/bin/bash traps_show_and_test() { local label="$1" echo; echo "TRAPS from $label:"; trap trap "echo modified by $label" SIGINT ## <-- MODIFICATION echo "TRAPS from $label (after modification attempt):"; trap } export -f traps_show_and_test traps_show_and_test main { traps_show_and_test subshell; } ( traps_show_and_test subparen ) bash -c 'traps_show_and_test bash_subprocess' traps_show_and_test job & wait { bash -c 'traps_show_and_test subshell_job'; } & wait ( bash -c 'traps_show_and_test subparen_job' ) & wait bash -c 'traps_show_and_test bash_job' & wait echo echo FINAL main traps: trap EOF chmod +x /tmp/test.sh /tmp/test.sh
Running the previous script in bash (version is 4.3.46(1)-release) gives the following:
TRAPS from main: TRAPS from main (after modification attempt): trap -- 'echo modified by main' SIGINT TRAPS from subshell: trap -- 'echo modified by main' SIGINT TRAPS from subshell (after modification attempt): trap -- 'echo modified by subshell' SIGINT TRAPS from subparen: trap -- 'echo modified by subshell' SIGINT TRAPS from subparen (after modification attempt): trap -- 'echo modified by subparen' SIGINT TRAPS from bash_subprocess: TRAPS from bash_subprocess (after modification attempt): trap -- 'echo modified by bash_subprocess' SIGINT TRAPS from job: trap -- 'echo modified by subshell' SIGINT TRAPS from job (after modification attempt): trap -- 'echo modified by job' SIGINT TRAPS from subshell_job: TRAPS from subshell_job (after modification attempt): trap -- 'echo modified by subshell_job' SIGINT TRAPS from subparen_job: TRAPS from subparen_job (after modification attempt): trap -- 'echo modified by subparen_job' SIGINT TRAPS from bash_job: trap -- '' SIGINT trap -- '' SIGQUIT TRAPS from bash_job (after modification attempt): trap -- '' SIGINT trap -- '' SIGQUIT FINAL main traps: trap -- 'echo modified by subshell' SIGINT
Let's highlight important points:
- Foreground Jobs
- subshells from {..} and (..) inherit their traps from the main scope.
- only {..} can modify the main scope.
- subprocesses do not inherit its trap from its parent process
- they all can modify at least locally their traps
- Background Jobs
- bash function inherits its traps from main scope but can't modify it (like (..))
- processes in {..} & and (..) & do not inherit traps
- direct background subprocess have default signed traps that are umodifiable
Bash documentation
As a reference, here are the documentation from bash related to these behaviors:
Process group id effect on background process (in Job Control section of doc):
[...] 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.
Default handler for SIGINT and SIGQUIT (in Signals section of doc):
Non-builtin commands run 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.
and about modification of traps (in trap builtin doc):
Signals ignored upon entry to the shell cannot be trapped or reset.