为 INT 设置陷阱在子 shell 中不起作用

为 INT 设置陷阱在子 shell 中不起作用
$ 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)

您能解释一下为什么SIGINT在第二种情况下没有调用处理程序吗?

答案1

作业控制是指允许用户在单个登录会话中在多个进程组(或作业)之间移动的协议。

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

通常它在交互式 shell 中启用,在非交互式 shell 中禁用:

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

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

在这种情况下......显然作业控制被禁用,并且$-不能依赖:

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

shell 将作业与每个管道关联起来。

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

也就是说,当启用作业控制时,每个管道都在单独的进程组中执行。

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

其中一项作业是前台作业,其余作业是后台作业。

后台工作有不应该被束缚在启动它们的外壳上。如果您退出 shell,它们将继续运行。因此,它们不应该被 中断SIGINT,而不是默认情况下。启用作业控制后,会自动完成,因为后台作业在单独的进程组中运行。当禁用作业控制时,bash使异步命令ignore SIGINT,并且不允许它们(如果它们是bash脚本)覆盖它。

也就是说,这里:

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

后台作业 ( bash -c "trap 'echo INT' INT; sleep 3") 由启用了作业控制的交互式 shell 执行。结果后台作业收到SIGINT.

当我们将其包装到没有作业控制的非交互式 shell 中时:

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

bash -c "trap 'echo INT' INT; sleep 3"忽略SIGINT,并且trap ... INT也被忽略。

这可以通过以下方式确认:

$ 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

一些相关的引用:

Bash 启动的非内置命令将信号处理程序设置为 shell 从其父级继承的值。当作业控制无效时,异步命令会忽略SIGINTSIGQUIT除了这些继承的处理程序之外。作为命令替换结果运行的命令将忽略键盘生成的作业控制信号SIGTTINSIGTTOUSIGTSTP

https://www.gnu.org/software/bash/manual/html_node/Signals.html

进入 shell 时被忽略的信号无法被捕获或重置。

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

作业控制是指有选择地停止(挂起)进程的执行并在稍后继续(恢复)其执行的能力。用户通常通过操作系统内核的终端驱动程序和 Bash 联合提供的交互界面来使用此功能。

shell 将作业与每个管道关联起来。它保存当前正在执行的作业的表,可以使用jobs命令列出该表。当 Bash 异步启动作业时,它会打印如下行:

[1] 25647

表示此作业是作业号1,并且与此作业关联的管道中最后一个进程的进程 ID 是25647。单个管道中的所有进程都是同一作业的成员。 Bash 使用作业抽象作为作业控制的基础。

为了便于实现作业控制的用户界面,操作系统维护当前终端进程组ID的概念。该进程组的成员(进程组 ID 等于当前终端进程组 ID 的进程)接收键盘生成的信号,例如SIGINT。据说这些进程位于前台。后台进程是指进程组ID与终端进程组ID不同的进程;这些进程不受键盘生成的信号的影响。仅允许前台进程从终端读取数据,或者如果用户用 指定stty tostop,则写入终端。尝试从终端读取(stty tostop有效时写入)的后台进程会由内核的终端驱动程序发送一个SIGTTIN( ) 信号,除非被捕获,否则该进程将挂起。SIGTTOU

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

相关内容