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.
1.
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.
(1)
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.
If 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 "$?"'
(with no 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 ^C
)
(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 sleep
nor sh
. bash
does not ignore SIGINT, but its handling is defered until sh
returns. You see Ouch
being displayed, not upon Ctrl+C, but after sleep
and 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 "$?"'
^C
$
the noninteractive bash
, and 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, trap
s are also reset in sub-shells.
2.
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.
When using wait
explicitly, 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 "$?"'
^COuch
130
In that case, sleep
and 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.