1
$ bash -c "trap \"echo INT\" INT; sleep 3" & pid=$!; sleep 1; kill -INT $pid; wait
[1] 27811
INT
[1]+  Done                    bash -c "trap \"echo INT\" INT; sleep 3"

$ (bash -c "trap \"echo INT\" INT; sleep 3" & pid=$!; sleep 1; kill -INT $pid; wait)

Can you explain why the SIGINT handler doesn't get invoked in the second case?

3
  • @zevzek That doesn't explain why bash -c "trap... works. Anyways, I've found that, "When job control is not in effect, asynchronous commands ignore SIGINT..." But they don't say, when it's not in effect.
    – x-yuri
    Nov 10, 2021 at 3:44
  • @zevzek Okay, if I take your wording, and if cmd in (cmd) is a script, than it explains things. The quote from the docs seems like it should explain things, but it doesn't. See the updated answer. On a side note, you can check out my solution to the X problem if you feel like it.
    – x-yuri
    Nov 10, 2021 at 8:17
  • @zevzek Well, I looked at $-. Surprisingly here -m is set, but job control is supposedly disabled: (echo $-; sleep 1 & fg). Anyway, I seem to have answered my question, particularly with your help, thanks.
    – x-yuri
    Nov 16, 2021 at 6:49

1 Answer 1

1

Job control refers to the protocol for allowing a user to move between multiple process groups (or jobs) within a single login session.

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

Generally it's enabled in interactive shells, and disabled in non-interactive ones:

$ echo $-; sleep 1 & fg
himBHs
[1] 84366
sleep 1

$ bash -c 'echo $-; sleep 1 & fg'
hBc
bash: line 1: fg: no job control

In this case... apparently job control is disabled, and $- can't be relied upon:

$ (echo $-; sleep 1 & fg)
himBHs
bash: fg: no job control

The shell associates a job with each pipeline.

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

That is, when job control is enabled each pipeline is executed in a separate process group.

pgid.sh:

#!/usr/bin/env bash
ps -o pgid= $$
$ ./pgid.sh >&2 | ./pgid.sh >&2; ./pgid.sh; ./pgid.sh & wait
  93439
  93439
  93443
[1] 93445
  93445
[1]+  Done                    ./a.sh

$ (./pgid.sh >&2 | ./pgid.sh >&2; ./pgid.sh; ./pgid.sh & wait)
  93749
  93749
  93749
  93749

One of the jobs is a foreground job, the rest are background ones.

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, bash makes the asynchronous commands ignore SIGINT, and doesn't let them (if they're bash scripts) override it.

That is, here:

$ bash -c "trap 'echo INT' INT; sleep 3" & pid=$!; sleep 1; kill -INT "$pid"; wait

the background job (bash -c "trap 'echo INT' INT; sleep 3") is executed by an interactive shell, which has job control enabled. As a result the background job receives SIGINT.

When we wrap it into a non-interactive shell without job control:

$ (bash -c "trap 'echo INT' INT; sleep 3" & pid=$!; sleep 1; kill -INT "$pid"; wait)

bash -c "trap 'echo INT' INT; sleep 3" ignores SIGINT, and trap ... INT is also ignored.

This can be confirmed this way:

$ bash -c "trap 'echo INT' INT; trap; sleep 3" & pid=$!; sleep 1; kill -INT "$pid"; wait
[1] 293631
trap -- 'echo INT' SIGINT
trap -- '' SIGFPE
INT
[1]+  Done                    bash -c "trap 'echo INT' INT; trap; sleep 3"

$ (bash -c "trap 'echo INT' INT; trap; sleep 3" & pid=$!; sleep 1; kill -INT "$pid"; wait)
trap -- '' SIGINT
trap -- '' SIGQUIT
trap -- '' SIGFPE

$ bash -c 'ps -o pid,ignored,comm,args -p $$' & wait
[1] 345833
    PID          IGNORED COMMAND         COMMAND
 345833 0000000000000000 ps              ps -o pid,ignored,comm,args -p 345833
[1]+  Done                    bash -c 'ps -o pid,ignored,comm,args -p $$'

$ (bash -c 'ps -o pid,ignored,comm,args -p $$' & wait)
    PID          IGNORED COMMAND         COMMAND
 345629 0000000000000006 ps              ps -o pid,ignored,comm,args -p 345629

A couple of relevant quotes:

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

Signals ignored upon entry to the shell cannot be trapped or reset.

https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html#index-trap

Job control refers to the ability to selectively stop (suspend) the execution of processes and continue (resume) their execution at a later point. A user typically employs this facility via an interactive interface supplied jointly by the operating system kernel’s terminal driver and Bash.

The shell associates a job with each pipeline. It keeps a table of currently executing jobs, which may be listed with the jobs command. When Bash starts a job asynchronously, it prints a line that looks like:

[1] 25647

indicating that this job is job number 1 and that the process ID of the last process in the pipeline associated with this job is 25647. All of the processes in a single pipeline are members of the same job. Bash uses the job abstraction as the basis for job control.

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.