为什么`kill -s INT` 与 `Ctrl-C` 的行为不同?

为什么`kill -s INT` 与 `Ctrl-C` 的行为不同?

从...开始:

% donothing () { echo $$; sleep 1000000 }
% donothing
47139

如果此时我点击了Ctrl-C控制 shell 的同一个终端,那么该函数donothing确实会终止,并且我会返回命令提示符。

但如果相反,从不同的 shell 会话,我运行

% kill -s INT 47139

...该donothing功能确实不是终止。 (如果我-47139作为最后一个参数传递给 ,则同上kill。)

我是否需要使用与我正在使用的 PID 不同的 PID 来实现与在控制终端kill -s INT交互地实现的相同效果?Ctrl-C

(我主要对 的答案感兴趣zsh。)


编辑:回应 Gilles 评论:不,我没有设置任何陷阱(尽管我有兴趣了解如何确认没有设置陷阱:有没有办法列出所有活动陷阱?);是的,我可以用 重现这种行为zsh -f。事实上,我可以用我知道的最极端的方式重现它,以获得绝对“裸露”的效果zsh(请让我知道是否有更好的方法):

% /usr/bin/env -i /usr/bin/zsh -f
% donothing () { echo $$; sleep 1000000 }
12345

...ETC。

自从我原来的帖子以来,我在 Darwin 和 NetBSD 下复制了上述行为,但不在 Linux 下(即在 Linux 下kill -s INT <zsh PID>确实杀死了donothing上面定义的函数,但没有杀死进程中的父 shell)。

所以也许这是一个 BSD 风格的事情。更具体地说,在我尝试过的每个 Unix 中,进程sleep都是进程的子进程zsh,但只有在 Linux 下,SIGINT发送到zsh进程的信号才会传播到sleep进程。

因此,为了能够donothing在 Darwin 和 NetBSD 下以非交互方式终止该函数,我需要一种方法来找到 -ing 子进程的 PID sleep,并将其发送SIGINT给它(诚然,这听起来确实很无情)。更一般地,要在 Darwin 和 NetBSD 下以非交互方式终止 shell 函数,我需要递归/深度优先找到后人流程zsh并将其发送SIGINT给他们...

答案1

Control-c从终端向与该终端关联的进程组发送中断信号,其中包括睡眠进程。通过kill发送SIGINT只能到达一个进程,在这种情况下,它恰好是错误的进程,因为$$返回shell的进程ID,而不是睡眠进程。 shell 通常会忽略 SIGINT。

答案2

基本上,你可以不是向特定的 shell 函数发送信号。函数是组成过程逻辑块的便捷工具。这与 shell 脚本类似,但不等同。

运行 shell 脚本时,当前运行的 shell/命令行解释器会分叉,使子 shell/解释器执行该脚本。分叉意味着创建一个全新的流程。该进程可以发送任意信号。

当运行 shell 函数时,不会有(隐式)分叉,因此没有专用进程,因此没有可以发送信号的目标。

当您点击Ctrl-C终端时,繁忙的进程是 shell 本身,它只会停止当前正在执行的操作。在您的示例中,甚至很难判断是否sleep收到信号(以便函数因到达末尾而退出)或函数是否停止执行。

$ domuch () { for (( i=0; i<1000000; i++)) {} }

无论如何也会被 阻止Ctrl-C,并且肯定没有任何信号可以发送到。

如果您想发送信号,则必须涉及子 shell,方法是将行放入脚本中,或者使用反引号(不知道如何在此处显示它们:-/)或替代符号$()

$ doless () { $(sleep 1000) }

您可以使用$!特殊参数来告诉您最近启动的后台进程的 PID,但只能在同一个 shell 中,然后您可以执行以下操作:

$ doless &
$ kill -int $!
[1] 30769
[1]  + interrupt  dosome

它仍然不是接收信号的函数,而是sleep接收信号的函数。

有趣的是,如果您从函数内部将 sleep 分派到后台,如下所示

dotoomuch()  () { $(sleep 1000) & }

你将无法通过 if 来中断它Ctrl-C,尽管它阻塞了终端。不知道这是否是期望的行为(zsh 4.3.10)。

相关内容