我正在运行这个 Bash 脚本:
#!/bin/bash
for i in $(seq 1 100);
do
echo $i
sleep 1
done
当我发出时kill -INT <pid>
什么也没有发生——脚本继续运行。如果我发出kill -TERM <pid>
,脚本和 sleep 命令都会被终止。<pid>
这里指的是脚本的 PID,而不是进程sleep
。
我想了解这里信号处理的区别。我知道发出Ctrl+C将实现与发送相同的结果,-TERM
因为信号被发送到进程组,但我不太明白为什么发出的-INT
行为与发出 不同-TERM
。据我所知,SIGINT 的默认操作是终止进程。
一些背景信息:我的用例是,此脚本作为另一个程序的子进程启动,并且该程序SIGINT
在关闭期间发送一个,然后等待 X 秒并发送一个SIGKILL
。我将 改为SIGINT
一个SIGTERM
,它突然终止了进程而不等待,这让我想到了这里的行为差异。
答案1
你观察到的是一种叫做等待并合作退出(WCE)。它是关于处理 SIGINT/SIGQUIT。
您可以在这里阅读有关 WCE (以及替代方法) 的信息:正确处理 SIGINT/SIGQUIT。这篇文章很全面。WCE 的简短描述来自格雷格的维基足以让我们明白:
bash 是少数几个实现等待并合作退出处理 SIGINT/SIGQUIT 传递的方法。解释脚本时,收到 SIGINT 后,它不会立即退出,而是等待当前正在运行的命令返回,并且只有当该命令也被该 SIGINT 终止时,它才会退出(通过使用 SIGINT 终止自身)。这个想法是,如果您的脚本调用
vi
,并且您在其中按Ctrl+取消操作,则不应将其视为中止脚本的请求。Cvi
这对你的剧本意味着什么?
如果<pid>
是脚本的 PID(即bash
解释脚本的 PID),则您kill -INT <pid>
仅向此进程发送 SIGINT。Ctrl+C或kill -INT -- -<pid>
(“负”PID)将向整个进程组发送 SIGINT。 SIGINT 在设计上是“键盘中断”(参见man 7 signal
),因此Ctrl+C是发送 SIGINT 的设计方式,因此将 SIGINT 发送到前台进程组(而不是发送到单身的进程(process)是 SIGINT 的设计用法。
当bash
解释脚本在等待sleep 1
返回时收到 SIGINT 时,它会处理信号并做出反应或不做出反应,具体取决于 的反应sleep
。实际上,shell 无法判断 是否sleep
也收到了信号;它假设。shell 假设信号来自Ctrl+ C(或等效的东西),因为这是 SIGINT 的设计用法;因此 shell 假设 currentsleep
也收到了信号。
如果sleep
真的收到了信号(即,如果您使用了Ctrl+C或kill -INT -- -<pid>
),那么它将因信号而终止。通常,进程能够判断其子进程是否被信号终止,因此这里 shell 能够判断哪个进程被sleep
信号终止。如果sleep
被 SIGINT 终止,shell 会断定用户已按下Ctrl+C以终止整个脚本,因此 shell 会使用 SIGINT 终止自身(注意:不仅仅是退出;它会通过停止处理信号并将信号发送给自身来终止自身,因此如果 shell 的父进程也实现了 WCE,那么它会相应地做出反应;依此类推)。
你sleep
没有收到信号,但bash
脚本解释器假设它已经收到。然后 shell 观察到sleep
已经正常退出(没有被信号终止),因此它sleep
假设处理或忽略信号;所以它假设是的,有Ctrl+ C,但作为普通的子进程的操作,而不是试图终止整个脚本。这就是为什么 shell 没有退出,而是继续解释脚本,就像什么都没发生一样,因为sleep
已经退出了,就像什么都没发生一样。
换句话说,实现 WCE 的父进程根据当前运行的子进程对 SIGINT/SIGQUIT 做出反应。通过向 发送 SIGINTbash
而不是向 发送sleep
,你有点欺骗了 shell:你让它假设sleep
已经处理或忽略了信号并且一切都很好,因此退出整个脚本是错误的做法。
我把 改为
SIGINT
aSIGTERM
,它就突然终止了进程,没有等待
WCE 是关于处理 SIGINT/SIGQUIT 的。对于 SIGTERM,没有这样的机制(我认为也不需要),所以这个信号“就起作用了”。