简体   繁体   English

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

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

Is there any way to invoke a subprocess so that it and all its descendants are sent an interrupt, just as if you Ctrl-C a foreground task?有没有办法调用一个子进程,以便它和它的所有后代都被发送一个中断,就像你 Ctrl-C 一个前台任务一样? I'm trying to kill a launcher script that invokes a long-running child.我试图杀死一个调用长期运行的孩子的启动器脚本。 I've tried kill -SIGINT $child (which doesn't send the interrupt to its descendants so is a no-op) and kill -SIGINT -$child (which works when invoked interactively but not when running in a script).我试过kill -SIGINT $child (它不会将中断发送给它的后代,所以是一个无操作)和kill -SIGINT -$child (它在交互调用时有效,但在脚本中运行时无效)。

Here's a test script.这是一个测试脚本。 The long-running script is test.sh --child .长时间运行的脚本是test.sh --child When you call test.sh --parent , it invokes test.sh --child & and then tries to kill it.当您调用test.sh --parent ,它会调用test.sh --child &然后尝试杀死它。 How can I make the parent kill the child successfully?我怎样才能让父母成功杀死孩子?

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

I know that you can modify the long-running child to trap signals, send them to its subprocess, and then wait (from Bash script kill background (grand)children on Ctrl+C ), but is there any way without modifying the child script?我知道你可以修改长时间运行的子进程来trap信号,将它们发送到它的子进程,然后等待(从Bash 脚本杀死后台(大)子进程在 Ctrl+C 上),但是有没有办法不修改子脚本?

For anyone wondering, this is how you launch childs in the background and kill them on 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 &

returns a pid of the child in $!$!返回子进程的 pid

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

I would start with this model rather than with bash job control (bg, fg, jobs).我将从这个模型开始,而不是从 bash 作业控制(bg、fg、jobs)开始。 Normally init inherits and reaps orphan processes.通常 init 会继承和收割孤儿进程。 What problem are you trying to solve?你想解决什么问题?

Read this : How to send a signal SIGINT from script to script ?阅读: 如何从脚本发送信号 SIGINT 到脚本? BASH 巴什

Also from info bash也来自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. 

So bash differentiates background processes from foreground processes by the process group ID .因此bash通过进程组 ID区分后台进程和前台进程。 If the process group id is equal to process id , then the process is a foreground process, and will terminate when it receives a SIGINT signal.如果进程组 id等于进程 id ,则该进程是前台进程,并在收到SIGINT信号时终止。 Otherwise it will not terminate (unless it is trapped).否则它不会终止(除非它被困)。

You can see the process group Id with您可以看到进程组 ID

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

Thus, when you run a background process (with & ) from within a script, it will ignore the SIGINT signal, unless it is trapped.因此,当您从脚本中运行后台进程(使用& )时,它将忽略SIGINT信号,除非它被捕获。

However, you can still kill the child process with other signals, such as SIGKILL , SIGTERM , etc.但是,您仍然可以使用其他信号杀死子进程,例如SIGKILLSIGTERM等。

For example, if you change your script to the following it will successfully kill the child process:例如,如果您将脚本更改为以下内容,它将成功终止子进程:

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

Output:输出:

$ ./test.sh --parent
kill 2187
./test.sh: line 10:  2187 Terminated              "$0" --child

You can keep using SIGINT with background tasks with an easy little twist: Put your asynchronous subprocess call in a function or { } , and give it setsid so it has its own process group.您可以通过一个简单的小SIGINT继续将SIGINT用于后台任务:将您的异步子进程调用放在一个函数或{ } ,并为其提供setsid以便它拥有自己的进程组。

Here's your script keep it's whole first intention:这是您的脚本,保持其全部初衷:

  • using and propagating SIGINT and not using another signal使用和传播SIGINT而不是使用其他信号

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

  • adding the code necessary to get the PID of your child instance, which is the only process in the background subshell.添加获取子实例PID所需的代码,这是后台子shell中的唯一进程。

Here's your code:这是你的代码:

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

Here's the important doc part from bash manual这是 bash 手册中重要的文档部分

Process group id effect on background process (in Job Control section of doc):进程组 id 对后台进程的影响(在文档的作业控制部分):

[...] processes whose process group ID is equal to the current terminal process group ID [..] receive keyboard-generated signals such as SIGINT. [...] 进程组 ID 等于当前终端进程组 ID [..] 的进程接收键盘生成的信号,例如 SIGINT。 These processes are said to be in the foreground.据说这些进程在前台。 Background processes are those whose process group ID differs from the terminal's;后台进程是那些进程组 ID 与终端不同的进程; such processes are immune to keyboard-generated signals .此类进程不受键盘生成信号的影响

Default handler for SIGINT and SIGQUIT (in Signals section of doc): SIGINTSIGQUIT默认处理程序(在文档的信号部分):

Non-builtin commands run by bash have signal handlers set to the values inherited by the shell from its parent. bash 运行的非内置命令将信号处理程序设置为 shell 从其父级继承的值。 When job control is not in effect, asynchronous commands ignore SIGINT and SIGQUIT in addition to these inherited handlers.当作业控制无效时,除了这些继承的处理程序之外,异步命令还会忽略 SIGINT 和 SIGQUIT

and about modification of traps (in trap builtin doc):以及关于陷阱的修改(在trap内置文档中):

Signals ignored upon entry to the shell cannot be trapped or reset .进入外壳时被忽略的信号不能被捕获或重置

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

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