bash脚本如何将Ctrl-C等效于后台任务?

时间:2013-02-04 21:59:26

标签: bash

有没有办法调用子进程,以便它及其所有后代发送一个中断,就像你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 script kill background (grand)children on Ctrl+C),但是没有修改子脚本的方法吗?

4 个答案:

答案 0 :(得分:10)

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

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

答案 1 :(得分:5)

somecommand &

$!

中返回子项的pid
somecommand &
pid[0]=$!
anothercommand &
pid[1]=$!
trap INT " kill ${pid[0]} ${pid[1]}; exit 1"
wait

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

答案 2 :(得分:5)

阅读本文:How to send a signal SIGINT from script to script ? 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. 

因此bash通过进程组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

答案 3 :(得分:1)

您可以继续使用SIGINT后台任务,只需轻轻一点:将异步子流程调用放在函数或{ }中,然后将其setsid赋予它它有自己的流程组。

在这里,你的剧本保留了它的第一个意图:

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

  • 仅修改从"$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对后台进程的影响(在doc的作业控制部分中):

  

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

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

  

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

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

  

进入shell时忽略的信号无法被捕获或重置