What does "a signal for which a trap has been set" mean?
That's a signal for which a handler has been defined (with
trap 'handling code' SIG) where the handling code is not empty as that would cause the signal to be ignored.
So signals that have their default disposition are not signals for which a trap has been set.
Some of that quote in your post also applies to signals that have their default disposition, though obviously not the part about running the trap, since no trap have been defined for them.
The manual talks about signal delivery to the shell, not to the commands you run from that shell.
If Bash is waiting for a command to complete and receives a signal for which a trap has been set, the trap will not be executed until the command completes.
why in my first example, does ctrl-C make the foreground job exit immediately before it can complete
If you run
sleep 10 at the prompt of an interactive shell, the shell will put that job in the foreground (through an
ioctl() on the tty device that tells the terminal line discipline which process group is the foreground one), so only
sleep will get a SIGINT upon
^C and the interactive shell will not, so it's not useful to test that behaviour.
The parent shell, as it's interactive won't receive the SIGINT as its process is not in the foreground process group.
Each command is free to handle signals as they please.
sleep doesn't do anything specially with SIGINT, so will get the default disposition (terminate) unless SIGINT was being ignored upon startup.
(2) If you run
sleep 10 in a non-interactive shell,
bash -c 'sleep 10; echo "$?"'
Both the noninteractive
bash shell and
sleep will receive the SIGINT when you press Ctrl-C.
bash exited straight away, it could leave the
sleep command running unattended in background if it happened to ignore or handle the SIGINT signal. So instead,
bash like most other shells, blocks the reception of signals (at least some signals) when waiting for commands.
- Delivery is resumed after the command exits (upon which traps are executed). That also avoids commands in traps to run concurrently with other commands.
In that example above,
sleep will die upon SIGINT, so
bash doesn't have long to wait to handle its own SIGINT (here to die as I didn't add a
trap on SIGINT).
(3) when you press Ctrl+C while running the noninteractive shell:
bash -c 'sh -c "trap \"\" INT; sleep 3"; echo "$?"'
trap on SIGINT)
bash is not killed by the SIGINT.
bash, like a few other shells treat SIGINT and SIGQUIT specially. They implement the wait and cooperative exit behaviour described at https://www.cons.org/cracauer/sigint.html (and is known to cause a few annoyances like the scripts calling SIGINT handling commands that can't be interrupted with
(4) To properly test, you should run a non-interactive
bash that has SIGINT trap set and calls a command that doesn't die straight away upon SIGINT like:
bash -c 'trap "echo Ouch" INT; sh -c "trap \"\" INT; sleep 3"'
bash is waiting for
sh that has (along with
sleep) SIGINT ignored (because of the
trap "" INT), so SIGINT won't kill
bash does not ignore SIGINT, but its handling is defered until
sh returns. You see
Ouch being displayed, not upon Ctrl+C, but after
sh have terminated normally.
Note that the
trap command sets a trap for a signal for the same shell in which it runs. So when the
trap command is executed outside the noninteractive shell and in the parent shell,
$ trap "echo You hit control-C!" INT
$ bash -c 'sleep 10; echo "$?"'
sleep commands won't inherit that
trap from the parent shell. Signal handlers are lost upon executing a different command (
execve() wipes the whole address space of the process including the code of the handler). Upon
execve(), signals that had a handler defined revert to the default disposition, those that were ignored remain ignored.
In addition to that, in most shells,
traps are also reset in sub-shells.
When Bash is waiting for an asynchronous command via the wait builtin, the reception of a signal for which a trap has been set will cause the wait builtin to return immediately with an exit status greater than 128, immediately after which the trap is executed.
wait is interrupted by any signal that has a trap set (and obviously also those that kill the shell altogether).
That makes it difficult to get the exit status of a command reliably when there are trapped signals:
$ bash -c 'trap "echo Ouch" INT; sh -c "trap \"\" INT; sleep 10" & wait "$!"; echo "$?"'
In that case,
sh were not killed by the SIGINT (since they ignore it). Still
wait returns with a
130 exit status because a signal (SIGINT) was received while it was waiting for
sh. You'd need to repeat the
wait "$!" until
sh really terminates to get
sh's exit status.