$ 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 从其父级继承的值。当作业控制无效时,异步命令会忽略
SIGINT
并SIGQUIT
除了这些继承的处理程序之外。作为命令替换结果运行的命令将忽略键盘生成的作业控制信号SIGTTIN
、SIGTTOU
和SIGTSTP
。
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