0

To understand

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.

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.

from Bash manual, I run the following:

  1. In my two examples, SIGINT (sent with Ctrl-C) terminates a foreground job (the first case in the quote) and a background job (the second case in the quote) both immediately, without waiting for them to complete.

    Does the first sentence in the quote mean that if Bash is running a foreground job and receives signal SIGINT, the trap of the signal SIGINT, when set, will be executed until the command completes? If yes, why in my first example, does ctrl-C make the foreground job exists immediately before it can complete?

    $ sleep 10000  # a foreground job
    ^C
    
    
    $ sleep 10000 & # a background job
    [1] 21219
    $ wait 21219
    ^C
    $ echo $?
    130
    
  2. What does "a signal for which a trap has been set" mean,

    • a signal arg whose trap has been specified via trap arg sigspec, or

    • a signal which is not ignored, or

    • a signal whose trap isn't the default one?

    In the examples in my part 1, I didn't set up a trap for SIGINT, so the signal has its default handler (which breaks out of any executing loop). Is a signal having its default handler considered to have a trap that has been set?

  3. I set up a trap for SIGINT, but ctrl-C will make the following command exit before completing. So is it contrary to the first sentence in my quote?

    $ trap "echo You hit control-C!" INT
    $ bash -c 'sleep 10; echo "$?"'
    ^C
    $
    

    Before setting the trap for SIGINT, ctrl-C will also make the same command exit before completing. So is it contrary to the first sentence in my quote?

    $ bash -c 'sleep 10; echo "$?"'
    ^C
    
  4. Could you give some examples to explain what the two sentences in the quote mean?

Thanks.

Tim
  • 101,790
  • 4
    You’re not setting any traps, so your tests don’t check the behaviour described in your quote. – Stephen Kitt Aug 08 '17 at 10:13
  • 3
    The subject line doesn't mention traps, but the quoted part of the manual is about traps, but-but the rest of the question sets up no traps; are we setting up traps for this question or not? – Jeff Schaller Aug 08 '17 at 10:59
  • @StephenKitt: Thanks. (1) What does "a signal for which a trap has been set" mean? (2) could you give an example which "check the behaviour described in the quote"? – Tim Aug 09 '17 at 03:55
  • Made more explanation. Please reopen. – Tim Aug 09 '17 at 04:08

1 Answers1

6

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, traps 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.

  • Thanks. I am trying to understand your reply, and still not sure if I understand. When I run bash -c 'sleep 10; echo "$?"' with the trap for SIGINT is set, ctrl-C will make it exit before completing. So is it contrary to the first sentence in my quote? – Tim Aug 09 '17 at 03:59
  • I have updated my post with more information. – Tim Aug 09 '17 at 04:13
  • Thanks for the edits. Sorry for taking the liberty to change the organization of your reply. As I still not yet completely understand, I might made some mistakes when editing. So please correct mine if you find some. – Tim Aug 09 '17 at 18:14
  • Because the first sentence from the bash manual quote says "If Bash is ... and receives a signal for which a trap has been set", I understand it is necessary to set the trap in the noninteractive shell, as in bash -c 'trap "echo Ouch" INT;...'. Some questions: (1) What is the purpose of the other trap set in the same shell as sleep, i.e. the trap in sh -c "trap \"\" INT; sleep 3"? (2) In "you should run a non-interactive bash that calls a command that intercepts SIGINT ", does "a command that intercepts SIGINT" mean sh -c "trap \"\" INT; sleep 3" in the example? – Tim Aug 09 '17 at 18:14
  • (3) in the two examples in parts 1(2) and 1(3) that I created in your reply, there is no SIGINT trap set in the noninteractive bash shell. So shouldn't the noninteractive bash shell behave in the same way, instead of behaving differently: ignores SIGINT it receives in part 1(2), while having the wait and cooperative exit behaviour in part 1(3)? – Tim Aug 09 '17 at 18:15
  • Thanks for the edit! When running bash -c 'trap "echo Ouch" INT; sh -c "trap \"\" INT; sleep 3"', all three processes bash, sh and sleep receive SIGINT. Is there an order between the three processes about which handles SIGINT before which, and does one's handling depend on previous one's handling? Shouldn't sleep exit right away on receiving SIGINT? So why "SIGINT won't kill sleep nor sh"? – Tim Aug 18 '17 at 11:46
  • I see. it is because the parent shell sh of sleep 3 has set up a trap to ignore SIGINT via trap \"\" INT;. Sorry for my stupid question. – Tim Aug 18 '17 at 16:53
  • @Tim, yes, sh and sleep will receive but ignore SIGINT. That's what trap "" INT does: set the signal disposition to SIG_IGN (inherited by child processes and preserved upon execve()). bash's own handling of SIGINT is deferred until sh returns. – Stéphane Chazelas Aug 18 '17 at 16:56