繁体   English   中英

bash 脚本如何对后台任务执行相当于 Ctrl-C 的操作?

[英]How can bash script do the equivalent of Ctrl-C to a background task?

有没有办法调用一个子进程,以便它和它的所有后代都被发送一个中断,就像你 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信号,将它们发送到它的子进程,然后等待(从Bash 脚本杀死后台(大)子进程在 Ctrl+C 上),但是有没有办法不修改子脚本?

对于任何想知道的人,这就是您如何在后台启动孩子并在 ctrl+c 上杀死他们的方法:

#!/usr/bin/env bash
command1 &
pid[0]=$!
command2 &
pid[1]=$!
trap "kill ${pid[0]} ${pid[1]}; exit 1" INT
wait
somecommand &

$!返回子进程的 pid

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

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

阅读: 如何从脚本发送信号 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. 

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

您可以看到进程组 ID

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

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

但是,您仍然可以使用其他信号杀死子进程,例如SIGKILLSIGTERM等。

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

#!/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

您可以通过一个简单的小SIGINT继续将SIGINT用于后台任务:将您的异步子进程调用放在一个函数或{ } ,并为其提供setsid以便它拥有自己的进程组。

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

  • 使用和传播SIGINT而不是使用其他信号

  • 只修改调用: "$0" --child &{ setsid "$0" --child; } & "$0" --child & { setsid "$0" --child; } & { 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 与终端不同的进程; 此类进程不受键盘生成信号的影响

SIGINTSIGQUIT默认处理程序(在文档的信号部分):

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM