当来自 bash 脚本的 运行 时,Perl 无法终止自身 pid

Perl fails to kill self pid when running from bash script

当 运行 来自终端 时,以下代码的行为符合预期:

perl -e 'kill -2, $$; warn HERE, $/'

它发送自己 SIGINT 并在到达 "HERE" 之前死亡:

~# perl -e 'kill -2, $$; warn HERE, $/'

~# echo $?
130
~#

问题:当 运行 来自 shell 脚本 :

时,相同的代码无法杀死自身 PID
~# cat 1.sh
perl -e 'kill -2, $$; warn HERE, $/'
~#
~# sh 1.sh
HERE
~#
~# echo $?
0
~#

另一方面,用 shell 替换 perl 的 kill 可以正常工作:

~# cat 2.sh
perl -e 'qx/kill -2 $$/; warn HERE, $/'
~#
~# sh 2.sh
~#
~# echo $?
130
~#

不太明白这里发生了什么,请帮忙..

这种方式效果很好:

perl -E 'say "kill "INT", $$; warn HERE, $/'

perl -E 'say "kill 2, $$; warn HERE, $/'

kill 手册页说:

A negative signal name is the same as a negative signal number, killing process groups instead of processes. For example, kill '-KILL', $pgrp and kill -9, $pgrp will send SIGKILL to the entire process group specified. That means you usually want to use positive not negative signals.

首先,

 kill -2, $$

最好写成

 kill 2, -$$

更好的选择是

 kill INT => -$$

这些发送SIGINT到指定的进程组。


您的主要问题似乎是为什么两个 shell 的行为不同。本节对此进行了解释。

进程组代表一个应用程序。

当您从交互式 shell 启动程序时,它不是较大应用程序的一部分,因此 shell 会为该程序创建一个新的进程组。

但是,由脚本(即非交互式 shell)创建的进程与脚本本身属于同一应用程序的一部分,因此 shell 不会创建新的进程组为了他们。

您可以使用以下方法将其可视化:

  • sh -i <<< 'perl -e '\''system ps => -o => "pid,ppid,pgrp,comm"'\''' 输出如下:

    $ perl -e 'system ps => -o => "pid,ppid,pgrp,comm"'
      PID  PPID  PGRP COMMAND
     8179  8171  8179 bash
    14654  8179 14654 sh
    14655 14654 14655 perl
    14656 14655 14655 ps
    
    $ exit
    

    在交互模式下,perlperlps的程序组中。

  • sh <<< 'perl -e '\''system ps => -o => "pid,ppid,pgrp,comm"'\''' 输出如下:

      PID  PPID  PGRP COMMAND
     8179  8171  8179 bash
    14584  8179 14584 sh
    14585 14584 14584 perl
    14586 14585 14584 ps
    

    在非交互模式下,shperlps的程序组中。


你的失败是因为没有将信号发送到进程组的头(即应用程序)。如果您检查过,报告的错误 killESRCH ("No such process").

ESRCH The pid or process group does not exist. [...]

杀掉当前进程的进程组,替换不合适的

kill INT => -$$           # XXX

kill INT => -getpgrp()    # Kill the application

您可以通过简单地调用以下命令使您的 perl 成为它自己的进程组的组长:

setpgrp();

测试:

$ sh <<< 'perl -e '\''system ps => ( -o => "pid,ppid,pgrp,comm" )'\'''
  PID  PPID  PGRP COMMAND
 8179  8171  8179 bash
16325  8179 16325 sh
16326 16325 16325 perl
16327 16326 16325 ps

$ sh <<< 'perl -e '\''setpgrp(); system ps => ( -o => "pid,ppid,pgrp,comm" )'\'''
  PID  PPID  PGRP COMMAND
 8179  8171  8179 bash
16349  8179 16349 sh
16350 16349 16350 perl
16351 16350 16350 ps

这不是你通常想做的事情。


最后是 Perl 代码

kill INT => -$pgrp

等效于 kill 命令行实用程序的以下调用:

kill -s INT -$pgrp
kill -INT -$pgrp
kill -2 -$pgrp

您的 qx// 程序中缺少 -,因此它将 SIGINT 发送到已识别的进程而不是已识别的程序组。

从您的交互式终端,perl 进程终止了它所属的进程组。 (shell 运行s perl 在其自己的进程组中。)shell 在 $? 中报告此异常终止:

t0   interactive shell (pid=123, pgrp=123)
       |
t1     +------> perl -e (pid=456, pgrp=456, parent=123)
       |          |
t2   (wait)      kill(-2, 456) (in perl, same as kill pgrp 456 w/ SIGINT)
       |          |
t3   (wait)     *SIGINT*
       |
t4   report $?

从您的shell 脚本,perl 进程杀死了一个(可能)不存在的进程组,然后成功退出。您的交互式 shell 创建了一个新进程组,其中 运行 您的 shell 脚本,然后该脚本 运行s perl 作为同一进程组中的子进程。

t0   shell (pid=123, pgrp=123)
       |
t1     +-------> shell:1.sh (pid=456, pgrp=456, parent=123)
       |          |
t2   (wait)       +-------------> perl -e (pid=789, pgrp=456, parent=456)
       |          |                |
t3   (wait)     (wait)            kill pgrp 789 with SIGINT (error: no such pgrp)
       |          |                |
t4   (wait)     (wait)            exit success
       |          |
t5   (wait)     exit success
       |
t6   report $?

在您的 反引号 (qx//) 示例 中,您的交互式 shell 启动了一个带有新进程组的 shell 进程。 (这里并不重要,但是进程 运行s perl 在其同一个进程组中。)然后 Perl 运行s 作为它自己的子系统 kill 命令,其语义不同于 perl kill。这个孙子命令直接将 SIGINT 发送到 perl PID,而不是将 SIGINT 发送到进程组。 Perl 终止,并且该退出代码作为脚本的退出代码传送,因为它是脚本中的最后一个命令。

这张图比上一张要忙一些:

t0   shell (pid=123, pgrp=123)
       |
t1     +-------> shell:2.sh (pid=456, pgrp=456, parent=123)
       |          |
t2   (wait)       +----------> perl -e (pid=789, pgrp=456, parent=456)
       |          |             |
t3   (wait)     (wait)          +---------> /bin/kill SIGINT 789
       |          |             |             |
t4   (wait)     (wait)         *SIGINT*      exit success
       |          |
t5   (wait)     return $?
       |
t6   report $?