[英]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 会继承和收割孤儿进程。 你想解决什么问题?
也来自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
信号,除非它被捕获。
但是,您仍然可以使用其他信号杀死子进程,例如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
您可以通过一个简单的小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 与终端不同的进程; 此类进程不受键盘生成信号的影响。
SIGINT
和SIGQUIT
默认处理程序(在文档的信号部分):
bash 运行的非内置命令将信号处理程序设置为 shell 从其父级继承的值。 当作业控制无效时,除了这些继承的处理程序之外,异步命令还会忽略 SIGINT 和 SIGQUIT 。
以及关于陷阱的修改(在trap
内置文档中):
进入外壳时被忽略的信号不能被捕获或重置。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.