12

有没有办法调用一个子进程,以便它和它的所有后代都被发送一个中断,就像你 Ctrl-C 一个前台任务一样?我试图杀死一个调用长期运行的孩子的启动器脚本。我已经尝试过kill -SIGINT $child(它不会将中断发送给它的后代,所以是无操作的)和kill -SIGINT -$child(它在交互调用时有效,但在脚本中运行时无效)。

这是一个测试脚本。长时间运行的脚本是test.sh --child. 当您调用时test.sh --parent,它会调用test.sh --child &然后尝试杀死它。如何让父母成功杀死孩子?

#!/bin/bash

if [ "$1" = "--child" ]; then
sleep 1000

elif [ "$1" = "--parent" ]; then
"$0" --child &
for child in $(jobs -p); do
  echo kill -SIGINT "-$child" && kill -SIGINT "-$child"
done
wait $(jobs -p)

else
echo "Must be invoked with --child or --parent."
fi

我知道您可以将长时间运行的子进程修改为trap信号,将它们发送到其子进程,然后等待(从 Ctrl+C 上的 Bash 脚本杀死背景(大)子进程),但是有什么办法不修改子脚本?

4

4 回答 4

13

对于任何想知道的人,这就是您在后台启动子进程并在 ctrl+c 上杀死它们的方式:

#!/usr/bin/env bash
command1 &
pid[0]=$!
command2 &
pid[1]=$!
trap "kill ${pid[0]} ${pid[1]}; exit 1" INT
wait
于 2013-10-09T14:26:20.927 回答
6
somecommand &

返回孩子的pid$!

somecommand &
pid[0]=$!
anothercommand &
pid[1]=$!
trap "kill ${pid[0]} ${pid[1]}; exit 1" INT
wait

我将从这个模型开始,而不是从 bash 作业控制(bg、fg、jobs)开始。通常 init 继承并收获孤儿进程。你想解决什么问题?

于 2013-02-04T22:20:52.180 回答
6

阅读本文:如何从脚本发送信号 SIGINT 到脚本?重击

也来自info bash

   To facilitate the implementation of the user interface to job  control,
   the operating system maintains the notion of a current terminal process
   group ID.  Members of this process group (processes whose process group
   ID is equal to the current terminal process group ID) receive keyboard-
   generated signals such as SIGINT.  These processes are said  to  be  in
   the  foreground.  Background processes are those whose process group ID
   differs from the terminal's; such processes are immune to keyboard-gen‐
   erated signals. 

因此,通过进程组 IDbash区分后台进程和前台进程。如果process group id等于process id,则该进程是前台进程,并在收到信号时终止。否则它不会终止(除非它被困住)。SIGINT

您可以看到进程组ID

ps x -o  "%p %r %y %x %c "

因此,当您&从脚本中运行后台进程(使用 )时,它将忽略该SIGINT信号,除非它被捕获。

但是,您仍然可以使用其他信号终止子进程,例如SIGKILL,SIGTERM等。

例如,如果您将脚本更改为以下内容,它将成功终止子进程:

#!/bin/bash

if [ "$1" = "--child" ]; then
  sleep 1000
elif [ "$1" = "--parent" ]; then
  "$0" --child &
  for child in $(jobs -p); do
    echo kill "$child" && kill "$child"
  done
  wait $(jobs -p)

  else
  echo "Must be invoked with --child or --parent."
fi

输出:

$ ./test.sh --parent
kill 2187
./test.sh: line 10:  2187 Terminated              "$0" --child
于 2013-02-04T22:36:16.107 回答
2

您可以通过SIGINT简单的小改动继续使用后台任务:将您的异步子进程调用放入函数 or{ }中,并给它setsid以使其拥有自己的进程组。

这是您的脚本,保持它的全部初衷:

  • 使用和传播SIGINT而不是使用另一个信号

  • 只修改调用 from: "$0" --child &to{ setsid "$0" --child; } &

  • 添加获取子实例 PID 所需的代码,这是后台子 shell 中的唯一进程。

这是你的代码:

#!/bin/bash

if [ "$1" = "--child" ]; then
sleep 1000

elif [ "$1" = "--parent" ]; then
{ setsid "$0" --child; } &
subshell_pid=$!
pids=$(ps -ax -o ppid,pid --no-headers |
    sed -r 's/^ +//g;s/ +/ /g' |
    grep "^$subshell_pid " | cut -f 2 -d " ");
for child in $pids;  do
  echo kill -SIGINT "-$child" && kill -SIGINT "-$child"
done
wait $subshell_pid

else
echo "Must be invoked with --child or --parent."

这是 bash 手册中的重要文档部分

进程组 id 对后台进程的影响(在文档的作业控制部分):

[...] 进程组 ID 等于当前终端进程组 ID [..] 的进程接收键盘生成的信号,例如 SIGINT。据说这些过程处于前台。 后台进程是那些进程组 ID 与终端不同的进程;这样的过程不受键盘产生的信号的影响。

SIGINT和的默认处理程序SIGQUIT(在文档的信号部分中):

bash 运行的非内置命令将信号处理程序设置为 shell 从其父级继承的值。当作业控制无效时,除了这些继承的处理程序之外,异步命令还会忽略 SIGINT 和 SIGQUIT 。

以及关于陷阱的修改(在trap内置文档中):

进入 shell 时忽略的信号不能被捕获或重置

于 2016-07-30T12:20:49.927 回答