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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14696427/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 23:18:13  来源:igfitidea点击:

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

bash

提问by yonran

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? 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).

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

Here's a test script. The long-running script is test.sh --child. When you call test.sh --parent, it invokes test.sh --child &and then tries to kill it. How can I make the parent kill the child successfully?

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

#!/bin/bash

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

elif [ "" = "--parent" ]; then
"
   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. 
" --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 trapsignals, 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),但是有没有办法不修改子脚本?

采纳答案by user000001

Read this : How to send a signal SIGINT from script to script ? BASH

阅读:如何从脚本发送信号 SIGINT 到脚本?巴什

Also from info bash

也来自 info bash

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

So bashdifferentiates background processes from foreground processes by the process group ID. If the process group idis equal to process id, then the process is a foreground process, and will terminate when it receives a SIGINTsignal. Otherwise it will not terminate (unless it is trapped).

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

You can see the process group Idwith

你可以看到进程组ID

#!/bin/bash

if [ "" = "--child" ]; then
  sleep 1000
elif [ "" = "--parent" ]; then
  "
$ ./test.sh --parent
kill 2187
./test.sh: line 10:  2187 Terminated              "
#!/usr/bin/env bash
command1 &
pid[0]=$!
command2 &
pid[1]=$!
trap "kill ${pid[0]} ${pid[1]}; exit 1" INT
wait
" --child
" --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

Thus, when you run a background process (with &) from within a script, it will ignore the SIGINTsignal, 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:

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

somecommand &

Output:

输出:

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

回答by vvo

For anyone wondering, this is how you launch childs in the background and kill them on ctrl+c:

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

#!/bin/bash

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

elif [ "" = "--parent" ]; then
{ setsid "##代码##" --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."

回答by jim mcnamara

##代码##

returns a pid of the child in $!

返回孩子的pid $!

##代码##

I would start with this model rather than with bash job control (bg, fg, jobs). Normally init inherits and reaps orphan processes. What problem are you trying to solve?

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

回答by vaab

You can keep using SIGINTwith background tasks with an easy little twist: Put your asynchronous subprocess call in a function or {}, and give it setsidso it has its own process group.

您可以通过SIGINT一个简单的小改动继续使用后台任务:将您的异步子进程调用放在一个函数或中{},并赋予它setsid自己的进程组。

Here's your script keep it's whole first intention:

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

  • using and propagating SIGINTand not using another signal

  • modifying only the calling from: "$0" --child &to { setsid "$0" --child; } &

  • adding the code necessary to get the PID of your child instance, which is the only process in the background subshell.

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

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

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

Here's your code:

这是你的代码:

##代码##

Here's the important doc part from bash manual

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

Process group id effect on background process (in Job Controlsection of doc):

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

[...] 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 processesare those whose process group ID differs from the terminal's; such processes are immune to keyboard-generated signals.

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

Default handler for SIGINTand SIGQUIT(in Signalssection of doc):

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

Non-builtin commands run by bash have signal handlers set to the values inherited by the shell from its parent. When job control is not in effect, asynchronous commands ignore SIGINT and SIGQUITin addition to these inherited handlers.

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

and about modification of traps (in trapbuiltin doc):

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

Signals ignored upon entry to the shell cannot be trapped or reset.

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