bash 如何终止所有子shell进程?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8363519/
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-18 01:07:58  来源:igfitidea点击:

How do I terminate all the subshell processes?

bashcygwin

提问by ykaganovich

I have a bash script to test how a server performs under load.

我有一个 bash 脚本来测试服务器在负载下的执行情况。

num=1
if [ $# -gt 0 ]; then
    num=
fi
for i in {1 .. $num}; do
    (while true; do
        { time curl --silent 'http://localhost'; } 2>&1 | grep real
    done) &
done        

wait

When I hit Ctrl-C, the main process exits, but the background loops keep running. How do I make them all exit? Or is there a better way of spawning a configurable number of logic loops executing in parallel?

当我按下 Ctrl-C 时,主进程退出,但后台循环继续运行。我如何让他们全部退出?或者是否有更好的方法来生成可配置数量的并行执行的逻辑循环?

回答by Russell Davis

Here's a simpler solution -- just add the following line at the top of your script:

这是一个更简单的解决方案——只需在脚本顶部添加以下行:

trap "kill 0" SIGINT

Killing 0sends the signal to all processes in the current process group.

Killing0向当前进程组中的所有进程发送信号。

回答by Craig McQueen

One way to kill subshells, but not self:

一种杀死子外壳但不是自我的方法:

kill $(jobs -p)

回答by ruakh

You need to use job control, which, unfortunately, is a bit complicated. If these are the only background jobs that you expect will be running, you can run a command like this one:

您需要使用job control,不幸的是,这有点复杂。如果这些是您希望运行的唯一后台作业,您可以运行如下命令:

jobs \
  | perl -ne 'print "\n" if m/^\[(\d+)\][+-]? +Running/;' \
  | while read -r ; do kill %"$REPLY" ; done

jobsprints a list of all active jobs (running jobs, plus recently finished or terminated jobs), in a format like this:

jobs以如下格式打印所有活动作业(正在运行的作业,以及最近完成或终止的作业)的列表:

[1]   Running                 sleep 10 &
[2]   Running                 sleep 10 &
[3]   Running                 sleep 10 &
[4]   Running                 sleep 10 &
[5]   Running                 sleep 10 &
[6]   Running                 sleep 10 &
[7]   Running                 sleep 10 &
[8]   Running                 sleep 10 &
[9]-  Running                 sleep 10 &
[10]+  Running                 sleep 10 &

(Those are jobs that I launched by running for i in {1..10} ; do sleep 10 & done.)

(这些是我通过运行启动的作业for i in {1..10} ; do sleep 10 & done。)

perl -ne ...is me using Perl to extract the job numbers of the running jobs; you can obviously use a different tool if you prefer. You may need to modify this script if your jobshas a different output format; but the above output is also on Cygwin, so it's very likely identical to yours.

perl -ne ...是我使用 Perl 来提取正在运行的作业的作业编号;如果您愿意,显然可以使用不同的工具。如果您jobs有不同的输出格式,您可能需要修改此脚本;但是上面的输出也在 Cygwin 上,所以它很可能与您的相同。

read -rreads a "raw" line from standard input, and saves it into the variable $REPLY. kill %"$REPLY"will be something like kill %1, which "kills" (sends an interrupt signal to) job number 1. (Not to be confused with kill 1, which would kill processnumber 1.) Together, while read -r ; do kill %"$REPLY" ; donegoes through each job number printed by the Perl script, and kills it.

read -r从标准输入读取“原始”行,并将其保存到变量$REPLY. kill %"$REPLY"将类似于kill %1,它“杀死”(发送中断信号到)作业号 1。(不要与kill 1,它会杀死进程号 1混淆。)一起while read -r ; do kill %"$REPLY" ; done遍历由 Perl 脚本打印的每个作业号,并杀死它。

By the way, your for i in {1 .. $num}won't do what you expect, since brace expansion is handled beforeparameter expansion, so what you have is equivalent to for i in "{1" .. "$num}". (And you can't have white-space inside the brace expansion, anyway.) Unfortunately, I don't know of a clean alternative; I think you have to do something like for i in $(bash -c "{1..$num}"), or else switch to an arithmetic for-loop or whatnot.

顺便说一句,你for i in {1 .. $num}不会做你期望的,因为大括号扩展是参数扩展之前处理的,所以你所拥有的相当于for i in "{1" .. "$num}". (无论如何,你不能在大括号扩展内有空格。)不幸的是,我不知道一个干净的选择;我认为你必须做类似的事情for i in $(bash -c "{1..$num}"),否则切换到算术for循环或诸如此类。

Also by the way, you don't need to wrap your while-loop in parentheses; &already causes the job to be run in a subshell.

顺便说一句,您不需要将 while 循环括在括号中;&已经导致作业在子外壳中运行。

回答by Haravikk

Bit of a late answer, but for me solutions like kill 0or kill $(jobs -p)go too far (kill all child processes).

有点迟到的答案,但对我来说,像kill 0kill $(jobs -p)走得太远的解决方案(杀死所有子进程)。

If you just want to make sure one specific child-process (and its own children) are tidied up then a better solution is to kill by process group (PGID) using the sub-process' PID, like so:

如果您只想确保整理了一个特定的子进程(及其自己的子进程),那么更好的解决方案是使用子进程的 PID 按进程组 (PGID) 杀死,如下所示:

set -m
./some_child_script.sh &
some_pid=$!

kill -- -${some_pid}

Firstly, the set -mcommand will enable job management (if it isn't already), this is important, as otherwise all commands, sub-shells etc. will be assigned to the same process group as your parent script (unlike when you run the commands manually in a terminal), and kill will just give a "no such process" error. This needs to be called before you run the background command you wish to manage as a group (or just call it at script start if you have several).

首先,该set -m命令将启用作业管理(如果尚未启用),这很重要,否则所有命令、子 shell 等将被分配到与父脚本相同的进程组(与运行命令时不同)在终端中手动),而 kill 只会给出“没有这样的过程”错误。这需要在您运行希望作为一个组管理的后台命令之前调用(或者如果您有多个,则在脚本启动时调用它)。

Secondly, note that the argument to killis negative, this indicates that you want to kill an entire process group. By default the process group ID is the same as the first command in the group, so we can get it by simply adding a minus sign in front of the PID we fetched with $!. If you need to get the process group ID in a more complex case, you will need to use ps -o pgid= ${some_pid}, then add the minus sign to that.

其次,注意 to 的参数kill是否定的,这表明您要杀死整个进程组。默认情况下,进程组 ID 与组中的第一个命令相同,因此我们可以通过在我们获取的 PID 前添加一个减号来获取它$!。如果您需要在更复杂的情况下获取进程组 ID,则需要使用ps -o pgid= ${some_pid},然后为其添加减号。

Lastly, note the use of the explicit end of options --, this is important, as otherwise the process group argument will be treated as an option (signal number), and killwill complain it doesn't have enough arguments. You only need this if the process group argument is the first one you wish to terminate.

最后,请注意选项的显式结尾的使用--,这很重要,否则进程组参数将被视为选项(信号编号),并且kill会抱怨它没有足够的参数。如果进程组参数是您希望终止的第一个参数,则您只需要这个。

Here is a simplified example of a background timeout process, and how to cleanup as much as possible:

这是后台超时过程的简化示例,以及如何尽可能多地清理:

#!/bin/bash
# Use the overkill method in case we're terminated ourselves
trap 'kill $(jobs -p | xargs)' SIGINT SIGHUP SIGTERM EXIT

# Setup a simple timeout command (an echo)
set -m
{ sleep 3600; echo "Operation took longer than an hour"; } &
timeout_pid=$!

# Run our actual operation here
do_something

# Cancel our timeout
kill -- -${timeout_pid} >/dev/null 2>&1
wait -- -${timeout_pid} >/dev/null 2>&1
printf '' 2>&1

This should cleanly handle cancelling this simplistic timeout in all reasonable cases; the only case that can't be handled is the script being terminated immediately (kill -9), as it won't get a chance to cleanup.

这应该在所有合理的情况下干净地处理取消这个简单的超时;唯一无法处理的情况是脚本立即终止 ( kill -9),因为它没有机会进行清理。

I've also added a wait, followed by a no-op (printf ''), this is to suppress "terminated" messages that can be caused by the killcommand, it's a bit of a hack, but is reliable enough in my experience.

我还添加了一个wait,然后是一个无操作 ( printf ''),这是为了抑制可能由kill命令引起的“终止”消息,这有点 hack,但根据我的经验足够可靠。

回答by ykaganovich

Here's my eventual solution. I'm keeping track of the subshell process IDs using an array variable, and trapping the Ctrl-C signal to kill them.

这是我的最终解决方案。我正在使用数组变量跟踪子外壳进程 ID,并捕获 Ctrl-C 信号以终止它们。

declare -a subs #array of subshell pids

function kill_subs() {
    for pid in ${subs[@]}; do
        kill $pid
    done
    exit 0 
}

num=1 if [ $# -gt 0 ]; then
    num= fi

for ((i=0;i < $num; i++)); do
    while true; do
       { time curl --silent 'http://localhost'; } 2>&1 | grep real
    done &

    subs[$i]=$! #grab the pid of the subshell 
done

trap kill_subs 1 2 15

wait