从...开始:
% 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)。