如何从 bash 脚本终止 Cygwin bash 中脚本的进程树

发布于 2024-07-13 11:53:13 字数 1345 浏览 7 评论 0原文

我有一个 Cygwin bash 脚本,我需要在某些条件下监视并终止它 - 具体来说,在创建某个文件之后。 然而,我很难弄清楚如何以与 Ctrl+C 相同的完整性级别准确地终止脚本。

这是一个简单的脚本(称为 test1),它除了等待终止之外几乎没有什么作用。

#!/bin/bash

test -f kill_me && rm kill_me

touch kill_me
tail -f kill_me

如果此脚本在前台运行,则 Ctrl+C 将终止 tail 和脚本本身。 如果脚本在后台运行,kill %1(假设是作业 1)也将终止 tail 和脚本。

但是,当我尝试从脚本执行相同的操作时,我发现只有运行该脚本的 bash 进程被终止,而 tail 则与其断开连接父母。 这是我尝试的一种方法(test2):

#!/bin/bash

test -f kill_me && rm kill_me

(
    touch kill_me
    tail -f kill_me
) &

while true; do
    sleep 1
    test -f kill_me && {
        kill %1
        exit
    }
done

如果运行此方法,则在后台运行的 bash 子 shell 会终止,但 tail 仍然挂起大约。

如果我使用像这样明确单独的脚本,它仍然不起作用(test3):

#!/bin/bash

test -f kill_me && rm kill_me

# assuming test1 above is included in the same directory
./test1 &

while true; do
    sleep 1
    test -f kill_me && {
        kill %1
        exit
    }
done

tail 仍然在该脚本之后徘徊正在运行。

在我的实际情况中,创建文件的过程并不是特别有用,所以我无法让它自行终止; 但是,通过查明它何时创建了特定文件,我可以知道可以终止它。 不幸的是,我无法使用简单的 killall 或等效方法,因为可能有多个实例正在运行,而我只想终止特定实例。

I have a Cygwin bash script that I need to watch and terminate under certain conditions - specifically, after a certain file has been created. I'm having difficulty figuring out how exactly to terminate the script with the same level of completeness that Ctrl+C does, however.

Here's a simple script (called test1) that does little more than wait around to be terminated.

#!/bin/bash

test -f kill_me && rm kill_me

touch kill_me
tail -f kill_me

If this script is run in the foreground, Ctrl+C will terminate both the tail and the script itself. If the script is run in the background, a kill %1 (assuming it is job 1) will also terminate both tail and the script.

However, when I try to do the same thing from a script, I'm finding that only the bash process running the script is terminated, while tail hangs around disconnected from its parent. Here's one way I tried (test2):

#!/bin/bash

test -f kill_me && rm kill_me

(
    touch kill_me
    tail -f kill_me
) &

while true; do
    sleep 1
    test -f kill_me && {
        kill %1
        exit
    }
done

If this is run, the bash subshell running in the background is terminated OK, but tail still hangs around.

If I use an explicitly separate script, like this, it still doesn't work (test3):

#!/bin/bash

test -f kill_me && rm kill_me

# assuming test1 above is included in the same directory
./test1 &

while true; do
    sleep 1
    test -f kill_me && {
        kill %1
        exit
    }
done

tail is still hanging around after this script is run.

In my actual case, the process creating files is not particularly instrumentable, so I can't get it to terminate of its own accord; by finding out when it has created a particular file, however, I can at that point know that it's OK to terminate it. Unfortunately, I can't use a simple killall or equivalent, as there may be multiple instances running, and I only want to kill the specific instance.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

゛清羽墨安 2024-07-20 11:53:13

/bin/kill(程序,而不是 bash 内置程序)将负数 PID 解释为“杀死进程组”,这也将获得所有子进程。

更改

kill %1

/bin/kill -- -$

对我有用。

/bin/kill (the program, not the bash builtin) interprets a negative PID as “kill the process group” which will get all the children too.

Changing

kill %1

to

/bin/kill -- -$

works for me.

眉黛浅 2024-07-20 11:53:13

亚当的链接让我找到了解决问题的方向,尽管并非没有一些小警告。

该脚本无法在 Cygwin 下未经修改地运行,因此我重写了它,并添加了更多选项。 这是我的版本:

#!/bin/bash

function usage
{
    echo "usage: $(basename $0) [-c] [-<sigspec>] <pid>..."
    echo "Recursively kill the process tree(s) rooted by <pid>."
    echo "Options:"
    echo "  -c        Only kill children; don't kill root"
    echo "  <sigspec> Arbitrary argument to pass to kill, expected to be signal specification"
    exit 1
}

kill_parent=1
sig_spec=-9

function do_kill # <pid>...
{
    kill "$sig_spec" "$@"
}

function kill_children # pid
{
    local target=$1
    local pid=
    local ppid=
    local i
    # Returns alternating ids: first is pid, second is parent
    for i in $(ps -f | tail +2 | cut -b 10-24); do
        if [ ! -n "$pid" ]; then
            # first in pair
            pid=$i
        else
            # second in pair
            ppid=$i
            (( ppid == target && pid != $ )) && {
                kill_children $pid
                do_kill $pid
            }
            # reset pid for next pair
            pid=
        fi
    done

}

test -n "$1" || usage

while [ -n "$1" ]; do
    case "$1" in
        -c)
            kill_parent=0
            ;;

        -*)
            sig_spec="$1"
            ;;

        *)
            kill_children $1
            (( kill_parent )) && do_kill $1
            ;;
    esac
    shift
done

唯一真正的缺点是 bash 在收到致命信号时打印出的有点难看的消息,即“终止”、“杀死”或“中断”(取决于您发送的内容)。 不过,我可以忍受批处理脚本中的这一点。

Adam's link put me in a direction that will solve the problem, albeit not without some minor caveats.

The script doesn't work unmodified under Cygwin, so I rewrote it, and with a couple more options. Here's my version:

#!/bin/bash

function usage
{
    echo "usage: $(basename $0) [-c] [-<sigspec>] <pid>..."
    echo "Recursively kill the process tree(s) rooted by <pid>."
    echo "Options:"
    echo "  -c        Only kill children; don't kill root"
    echo "  <sigspec> Arbitrary argument to pass to kill, expected to be signal specification"
    exit 1
}

kill_parent=1
sig_spec=-9

function do_kill # <pid>...
{
    kill "$sig_spec" "$@"
}

function kill_children # pid
{
    local target=$1
    local pid=
    local ppid=
    local i
    # Returns alternating ids: first is pid, second is parent
    for i in $(ps -f | tail +2 | cut -b 10-24); do
        if [ ! -n "$pid" ]; then
            # first in pair
            pid=$i
        else
            # second in pair
            ppid=$i
            (( ppid == target && pid != $ )) && {
                kill_children $pid
                do_kill $pid
            }
            # reset pid for next pair
            pid=
        fi
    done

}

test -n "$1" || usage

while [ -n "$1" ]; do
    case "$1" in
        -c)
            kill_parent=0
            ;;

        -*)
            sig_spec="$1"
            ;;

        *)
            kill_children $1
            (( kill_parent )) && do_kill $1
            ;;
    esac
    shift
done

The only real downside is the somewhat ugly message that bash prints out when it receives a fatal signal, namely "Terminated", "Killed" or "Interrupted" (depending on what you send). However, I can live with that in batch scripts.

青朷 2024-07-20 11:53:13

这个脚本看起来可以工作:

#!/bin/bash
# Author: Sunil Alankar

##
# recursive kill. kills the process tree down from the specified pid
#

# foreach child of pid, recursive call dokill
dokill() {
    local pid=$1
    local itsparent=""
    local aprocess=""
    local x=""
    # next line is a single line
    for x in `/bin/ps -f | sed -e '/UID/d;s/[a-zA-Z0-9_-]\{1,\}
\{1,\}\([0-9]\{1,\}\) \{1,\}\([0-9]\{1,\}\) .*/\1 \2/g'`
    do
        if [ "$aprocess" = "" ]; then
            aprocess=$x
            itsparent=""
            continue
        else
            itsparent=$x
            if [ "$itsparent" = "$pid" ]; then
                dokill $aprocess
            fi
            aprocess=""
        fi
    done
    echo "killing $1"
    kill -9 $1 > /dev/null 2>&1
}

case $# in
1) PID=$1
        ;;
*) echo "usage: rekill <top pid to kill>";
        exit 1;
        ;;
esac

dokill $PID 

This script looks like it'll do the job:

#!/bin/bash
# Author: Sunil Alankar

##
# recursive kill. kills the process tree down from the specified pid
#

# foreach child of pid, recursive call dokill
dokill() {
    local pid=$1
    local itsparent=""
    local aprocess=""
    local x=""
    # next line is a single line
    for x in `/bin/ps -f | sed -e '/UID/d;s/[a-zA-Z0-9_-]\{1,\}
\{1,\}\([0-9]\{1,\}\) \{1,\}\([0-9]\{1,\}\) .*/\1 \2/g'`
    do
        if [ "$aprocess" = "" ]; then
            aprocess=$x
            itsparent=""
            continue
        else
            itsparent=$x
            if [ "$itsparent" = "$pid" ]; then
                dokill $aprocess
            fi
            aprocess=""
        fi
    done
    echo "killing $1"
    kill -9 $1 > /dev/null 2>&1
}

case $# in
1) PID=$1
        ;;
*) echo "usage: rekill <top pid to kill>";
        exit 1;
        ;;
esac

dokill $PID 
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文