Как завершить дерево процессов скрипта в Cygwin bash из скрипта bash - PullRequest
3 голосов
/ 07 февраля 2009

У меня есть сценарий 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, работающая в фоновом режиме, завершается нормально, но 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 или его эквивалент, так как может быть запущено несколько экземпляров, и я хочу убить только конкретный экземпляр.

Ответы [ 3 ]

6 голосов
/ 09 февраля 2009

/ bin / kill (программа, а не встроенная в bash) интерпретирует отрицательный PID как «уничтожить группу процессов», которая также получит всех детей.

Изменение

kill %1

до

/bin/kill -- -$$

работает для меня.

2 голосов
/ 07 февраля 2009

Ссылка Адама направила меня в направлении, которое решит проблему, хотя и не без незначительных предостережений.

Скрипт не работает без изменений в 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 печатает, когда получает фатальный сигнал, а именно: «Прервано», «Убито» или «Прервано» (в зависимости от того, что вы отправляете). Однако я могу жить с этим в пакетных сценариях.

1 голос
/ 07 февраля 2009

Этот скрипт выглядит так, как будто он сделает свою работу:

#!/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 
...