#!/usr/bin/env bash
sleep 3 && echo '123' &
sleep 3 && echo '456' &
sleep 3 && echo '999' &
如果我运行它,并SIGINT
通过终端按 control-c 发送,它似乎仍然回显输出123...
。我认为这是因为它以某种方式分离?
wait < <(jobs -p)
但是,如果我在脚本末尾添加(等待所有后台作业完成),然后运行它并发送,SIGINT
则123...
输出为不是显示。
如何解释这种行为?是否wait
以某种方式拦截信号并将其传递给后台进程?或者它与进程是否“连接”到终端的某种状态有关?
我发现了一个可能与此相关的问题,但我无法弄清楚它与上述行为有何关系:为什么使用 wait() 函数时不会忽略 SIGCHLD 信号?
答案1
信号拦截?传播?不
wait
不拦截信号。shell 不会传递它。信号既不会被拦截也不会传播。运行三个“后台”命令的子 shell 要么获取信号直接地或不。
您可以使用以下脚本进行测试:
#!/usr/bin/env bash
printf 'My PID is %s.\n' "$$"
sleep 15 && echo '123' &
sleep 15 && echo '456' &
sleep 15 && echo '999' &
wait < <(jobs -p)
该脚本的行为类似于带有wait
.我的意思是你可以终止它和Ctrl带有+ 的三个“后台”作业C。
再次运行它,不要点击Ctrl+ C。该脚本将告诉您它自己的 PID ( $$
)。现在,如果您SIGINT
从另一个终端 ( kill -s INT <pid_here>
) 发送,那么它将终止脚本,但是不是三个工作。
但是如果您发送SIGINT
到整个进程组 ( kill -s INT -- -<pid_here>
) 那么脚本和工作会得到它。当您按Ctrl+C并且您的终端设置为在击键时发送中断信号(通常是,stty -a
包括intr = ^C
)时,也会发生同样的情况,整个团体接收信号。
谁收到信号?
这是关于前台进程组的。 shell 执行的任务之一是通知终端哪个进程组位于前台。
终端可以具有与其关联的前台进程组。该前台进程组在处理信号生成输入字符方面发挥着特殊作用[...]。
支持作业控制的命令解释器进程可以通过将相关进程放置在单个进程组中并将该进程组与终端相关联,将终端分配给不同的作业或进程组。 […]
(来源)
这就是您的案例中真正发生的情况:
- 最初您处于交互式 shell 中。 shell是自己进程组的领导者(进程组ID等于shell的PID)。终端将此进程组识别为前台进程组。
- 上述 shell 启用了作业控制。 (一般来说,支持作业控制的 shell 在交互运行时默认启用它。)当您启动脚本(或基本上任何非内置脚本)时,shell 会使其成为另一个进程组的领导者。终端被告知新进程组现在应被视为前台进程组。交互式 shell 通过这种方式将自己置于后台。
- 从 shebang 我们知道它正在
bash
解释脚本。非交互式 Bash 启动时默认禁用作业控制。它将运行的命令不是成为自己的进程组的领导者(除非命令本身更改其进程组;这是可能的,但在您的情况下不会发生)。这三个子shell和三个sleep
进程与运行脚本属于同一个进程组。 - 当脚本终止时,终端会被告知交互式 shell 的进程组现在应该被视为前台进程组(再次)。如果脚本进程组中的任何进程仍在运行,它将不再位于前台进程组中。
这解释了您观察到的行为:
Ctrl如果当你点击+时脚本正在运行,C那么它和基本上它的所有后代都会收到
SIGINT
.脚本等待是因为wait
或因为某些没有&
.重要的是它的进程组仍然是前台进程组,并且(孙)子进程属于该组。Ctrl如果当您点击+时脚本不再运行,C那么它的后代(如果有)将不会收到,
SIGINT
因为它们不属于当前的前台进程组。
玩转作业控制
请注意,您可以通过禁用或启用作业控制来更改此行为。
如果您在交互式 shell 中禁用作业控制 ( set +m
) 并运行脚本,则 shell 将运行该脚本,但不会使其成为进程组的领导者。脚本中没有作业控制。该脚本及其所有子脚本基本上都属于交互式 shell 的进程组。该组将始终是前台进程组。Ctrl+之后,C所有进程(包括交互式 shell)都将收到SIGINT
,无论脚本是否仍在运行。
set -m
如果您在脚本本身中启用作业控制 ( ),则三个子 shell 将被放入各自的进程组中。在类似的情况下,没有的命令&
将成为进程组领导者,并且终端将被告知新的前台进程组。但是你的命令是&
,他们将成为领导者,但前台进程组不会改变。Ctrl+后,无论脚本是否仍在运行,也无论交互式 shell 是否启用了作业控制,C它们都不会收到。SIGINT
笔记
分隔符或终止符的含义
&
通常被描述为“在后台运行”,但它并不等同于“不在前台进程组中运行”。运行的命令&
可以保留在前台进程组中(例如,如果禁用作业控制)。不运行的命令&
可以离开前台进程组。您可以确定的是&
“异步运行”。您可能会对交互式 shell 放置的事实感到惊讶本身运行命令时在后台运行。这确实发生了。在交互式 Bash 中运行这些命令:
set -m trap 'echo "Signal received."' INT sleep 999
Ctrl+C
您将看到
sleep
被中断,但 shell 没有收到信号。这是因为 shell 放入了sleep
一个单独的进程组,该进程组是击键时的前台进程组。 shell 不在(当时的)前台进程组中,这意味着它在后台。现在更改
set -m
为set +m
并再次运行:set +m trap 'echo "Signal received."' INT sleep 999
Ctrl+C
禁用作业控制后,
sleep
将在 shell 的进程组中运行。该组将始终是前台进程组。您将看到一条来自 的消息trap
。