Який найкращий спосіб надіслати сигнал усім членам групи процесів?


422

Я хочу вбити ціле дерево процесу. Який найкращий спосіб зробити це за допомогою будь-яких загальних мов сценаріїв? Я шукаю просте рішення.


3
Зомбі повинні зникнути, коли системний жнець працює. Я визнаю, що бачив системи, де зомбі затримуються, але це нетипово.
Брайан Ноблеуч

7
Іноді ті затяжні зомбі відповідають за якусь страшну діяльність.
Користувач1

Скористайтеся однією з команд chronosабо herodes.
Майкл Ле Барб'є Грюневальд

8
kill $ (pstree <PID> -p -a -l | cut -d, -f2 | cut -d '' -f1)
vrdhn

@ MichaelLeBarbierGrünewald Не могли б ви зв’язатись із цими програмами?
пінопласт летить

Відповіді:


305

Ви не кажете, чи дерево, яке ви хочете вбити, - це одна група процесів. (Це часто трапляється, якщо дерево є результатом розгалуження із запуску сервера або командного рядка оболонки.) Ви можете виявити групи процесів за допомогою GNU ps таким чином:

 ps x -o  "%p %r %y %x %c "

Якщо це група процесів, яку ви хочете вбити, просто використовуйте kill(1)команду, але замість того, щоб давати їй номер процесу, надайте їй заперечення номера групи. Наприклад, щоб вбити кожен процес у групі 5112, використовуйте kill -TERM -- -5112.


3
kill -74313 -bash: kill: 74313: недійсна специфікація сигналу Якщо я додам kill -15 -GPID, він працював ідеально.
Адам Пек

51
Як звичайно з майже будь-якою командою, якщо ви хочете, щоб звичайний аргумент, який починається з - не слід інтерпретувати як перемикач, передуйте цьому
ysth

9
pgrepможе запропонувати більш простий спосіб знайти ідентифікатор групи процесів. Наприклад, щоб вбити групу процесу my-script.sh, запустіть kill -TERM -$(pgrep -o my-script.sh).
Джош Келлі

9
Краще подивіться на stackoverflow.com/questions/392022/…, це набагато більш елегантне рішення, і якщо вам потрібно перелічити підказки дітей, тоді використовуйте:ps -o pid --no-headers --ppid $PARENT_PID
Szymon Jeż

4
І якщо ви трохи модифікуєте формат і сортуєте, ви побачите всі процеси, які добре згруповані, і починаються з (потенційно) батьківського групи в кожній групі:ps x -o "%r %p %y %x %c" | sort -nk1,2
haridsv

199

Вбийте всі процеси, що належать до одного дерева процесів, використовуючи ідентифікатор групи процесів ( PGID)

  • kill -- -$PGID     Використовувати сигнал за замовчуванням ( TERM= 15)
  • kill -9 -$PGID     Використовуйте сигнал KILL(9)

Ви можете отримати PGIDз будь - якого Process-ID ( PID) того самого дерева процесів

  • kill -- -$(ps -o pgid= $PID | grep -o '[0-9]*')   (сигнал TERM)
  • kill -9 -$(ps -o pgid= $PID | grep -o '[0-9]*')   (сигнал KILL)

Окреме спасибі Tanager і Speakus для вкладів на $PIDінших просторах і сумісності OSX.

Пояснення

  • kill -9 -"$PGID"=> Надіслати сигнал 9 ( KILL) всім дітям та онукам ...
  • PGID=$(ps opgid= "$PID")=> Отримайте ідентифікатор Process-Group-групи з будь - якого процесу-ідентифікатора дерева, а не тільки Process-Parent-ID . Варіант того ps opgid= $PID, ps -o pgid --no-headers $PIDде pgidйого можна замінити pgrp.
    Але:
    • psвставляє провідні пробіли, коли PIDменше п'яти цифр і вирівнюється праворуч, як помічено tanager . Ви можете використовувати:
      PGID=$(ps opgid= "$PID" | tr -d ' ')
    • psз OSX завжди друкують заголовок, тому Speakus пропонує:
      PGID="$( ps -o pgid "$PID" | grep [0-9] | tr -d ' ' )"
  • grep -o [0-9]* друкує лише послідовні цифри (не друкує пробіли чи алфавітні заголовки).

Подальші командні рядки

PGID=$(ps -o pgid= $PID | grep -o [0-9]*)
kill -TERM -"$PGID"  # kill -15
kill -INT  -"$PGID"  # correspond to [CRTL+C] from keyboard
kill -QUIT -"$PGID"  # correspond to [CRTL+\] from keyboard
kill -CONT -"$PGID"  # restart a stopped process (above signals do not kill it)
sleep 2              # wait terminate process (more time if required)
kill -KILL -"$PGID"  # kill -9 if it does not intercept signals (or buggy)

Обмеження

  • Як зауважив Девід і Юбер Каріо , коли killйого викликає процес, що належить до того самого дерева,kill ризикує вбити себе, перш ніж припинити вбивство всього дерева.
  • Тому не забудьте запустити команду, використовуючи процес, що має інший ідентифікатор Process-Group-ID .

Довга історія

> cat run-many-processes.sh
#!/bin/sh
echo "ProcessID=$$ begins ($0)"
./child.sh background &
./child.sh foreground
echo "ProcessID=$$ ends ($0)"

> cat child.sh
#!/bin/sh
echo "ProcessID=$$ begins ($0)"
./grandchild.sh background &
./grandchild.sh foreground
echo "ProcessID=$$ ends ($0)"

> cat grandchild.sh
#!/bin/sh
echo "ProcessID=$$ begins ($0)"
sleep 9999
echo "ProcessID=$$ ends ($0)"

Запустіть дерево процесу у фоновому режимі, використовуючи "&"

> ./run-many-processes.sh &    
ProcessID=28957 begins (./run-many-processes.sh)
ProcessID=28959 begins (./child.sh)
ProcessID=28958 begins (./child.sh)
ProcessID=28960 begins (./grandchild.sh)
ProcessID=28961 begins (./grandchild.sh)
ProcessID=28962 begins (./grandchild.sh)
ProcessID=28963 begins (./grandchild.sh)

> PID=$!                    # get the Parent Process ID
> PGID=$(ps opgid= "$PID")  # get the Process Group ID

> ps fj
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
28348 28349 28349 28349 pts/3    28969 Ss   33021   0:00 -bash
28349 28957 28957 28349 pts/3    28969 S    33021   0:00  \_ /bin/sh ./run-many-processes.sh
28957 28958 28957 28349 pts/3    28969 S    33021   0:00  |   \_ /bin/sh ./child.sh background
28958 28961 28957 28349 pts/3    28969 S    33021   0:00  |   |   \_ /bin/sh ./grandchild.sh background
28961 28965 28957 28349 pts/3    28969 S    33021   0:00  |   |   |   \_ sleep 9999
28958 28963 28957 28349 pts/3    28969 S    33021   0:00  |   |   \_ /bin/sh ./grandchild.sh foreground
28963 28967 28957 28349 pts/3    28969 S    33021   0:00  |   |       \_ sleep 9999
28957 28959 28957 28349 pts/3    28969 S    33021   0:00  |   \_ /bin/sh ./child.sh foreground
28959 28960 28957 28349 pts/3    28969 S    33021   0:00  |       \_ /bin/sh ./grandchild.sh background
28960 28964 28957 28349 pts/3    28969 S    33021   0:00  |       |   \_ sleep 9999
28959 28962 28957 28349 pts/3    28969 S    33021   0:00  |       \_ /bin/sh ./grandchild.sh foreground
28962 28966 28957 28349 pts/3    28969 S    33021   0:00  |           \_ sleep 9999
28349 28969 28969 28349 pts/3    28969 R+   33021   0:00  \_ ps fj

Команда pkill -P $PIDне вбиває онука:

> pkill -P "$PID"
./run-many-processes.sh: line 4: 28958 Terminated              ./child.sh background
./run-many-processes.sh: line 4: 28959 Terminated              ./child.sh foreground
ProcessID=28957 ends (./run-many-processes.sh)
[1]+  Done                    ./run-many-processes.sh

> ps fj
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
28348 28349 28349 28349 pts/3    28987 Ss   33021   0:00 -bash
28349 28987 28987 28349 pts/3    28987 R+   33021   0:00  \_ ps fj
    1 28963 28957 28349 pts/3    28987 S    33021   0:00 /bin/sh ./grandchild.sh foreground
28963 28967 28957 28349 pts/3    28987 S    33021   0:00  \_ sleep 9999
    1 28962 28957 28349 pts/3    28987 S    33021   0:00 /bin/sh ./grandchild.sh foreground
28962 28966 28957 28349 pts/3    28987 S    33021   0:00  \_ sleep 9999
    1 28961 28957 28349 pts/3    28987 S    33021   0:00 /bin/sh ./grandchild.sh background
28961 28965 28957 28349 pts/3    28987 S    33021   0:00  \_ sleep 9999
    1 28960 28957 28349 pts/3    28987 S    33021   0:00 /bin/sh ./grandchild.sh background
28960 28964 28957 28349 pts/3    28987 S    33021   0:00  \_ sleep 9999

Команда kill -- -$PGIDвбиває всі процеси, включаючи онука.

> kill --    -"$PGID"  # default signal is TERM (kill -15)
> kill -CONT -"$PGID"  # awake stopped processes
> kill -KILL -"$PGID"  # kill -9 to be sure

> ps fj
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
28348 28349 28349 28349 pts/3    29039 Ss   33021   0:00 -bash
28349 29039 29039 28349 pts/3    29039 R+   33021   0:00  \_ ps fj

Висновок

Я помічаю в цьому прикладі PIDі PGIDрівні ( 28957).
Ось чому я спочатку вважав, що цього kill -- -$PIDдостатньо. Але в разі , коли процес ікра в межах ідентифікатор процесу відрізняється від ID групи .Makefile

Я думаю kill -- -$(ps -o pgid= $PID | grep -o [0-9]*), що найкращий простий трюк, щоб убити все дерево процесу, коли його викликають з іншого ідентифікатора групи (іншого дерева процесів).


1
Привіт @davide. Хороше питання. Думаю, killзавжди слід надсилати сигнал усьому дереву, перш ніж отримувати власний сигнал. Але в деяких конкретних обставинах / реалізаціях killможе надсилати до себе сигнал, бути перерваним і потім отримувати власний сигнал. Однак ризик повинен бути досить мінімальним, і він може бути проігнорований у більшості випадків, оскільки перед цим виникають інші помилки. Чи можна ігнорувати цей ризик у вашому випадку? Більше того, інші відповіді мають цю загальну помилку ( killчастина процесного дерева вбивається). Сподіваюся, що це допоможе .. Будьте здорові;)
олибре

3
Це працює лише в тому випадку, якщо самі підкоманди не стають лідерами груп. Навіть такі прості інструменти, як manце роблять. З іншого боку, якщо ви хочете вбити процес підлітків із дочірнього процесу, kill -- -$pidне вийде. Тож це не загальне рішення.
Хуберт Каріо

1
Приклад - «дитина», яка намагається вбити своїх дітей (так онуки користувача ініціювали команду). IOW, спробуйте вбити ієрархію фонових процесів у child.sh.
Хуберт Каріо

1
> kill -QUIT - "$ PGID" # той же сигнал, що і [CRTL + C] з клавіатури ---- QUIT слід замінити на INT, щоб це було правдою
Максим Холявкін,

1
для опції OSX - no-headers не підтримується, тому код слід оновити до: PGID = "$ (ps -o pgid" $ PID "| grep [0-9] | tr -d '')"
Максим Холявкін,

166
pkill -TERM -P 27888

Це знищить усі процеси, які мають ідентифікатор батьківського процесу 27888.

Або більш надійний:

CPIDS=$(pgrep -P 27888); (sleep 33 && kill -KILL $CPIDS &); kill -TERM $CPIDS

який планує вбити 33 секунди пізніше і ввічливо попросити припинити процеси.

Дивіться цю відповідь щодо припинення всіх нащадків.


19
У моєму швидкому тесті pgrep повідомив лише про найближчих дітей, тому це може не вбити всю ієрархію.
haridsv

10
Я погоджуюся з @haridsv: pkill -Pпередає сигнал лише дитині => онук не отримує сигнал => Тому я роздратував ще одну відповідь, щоб пояснити це. Ура ;-)
олібре

1
Із баш сценарію, щоб вбити власних дітей, використовуйте pkill -TERM -P ${$}.
Франсуа Бозолей

3
@Onlyjob, чи не небезпечно спати, то вбивати? Ідентифікатори процесу можуть бути використані ОС тим часом: ви можете вбивати процеси, які вже не є вашими дітьми. Я підозрюю, що заклик до pkill доведеться робити ще раз, щоб забезпечити це.
Франсуа Бозолей

2
@Onlyjob FYI, я можу породжувати 32768 процесів, однопотокові, з легким доступом вводу / виводу менш ніж за 19 секунд на моїй машині:$ time for i in {1..32768}; do ( echo $BASHPID >> pids ); done real 0m18.860s
cychoi

102

Для рекурсивного вбивства дерево процесів використовуйте killtree ():

#!/bin/bash

killtree() {
    local _pid=$1
    local _sig=${2:--TERM}
    kill -stop ${_pid} # needed to stop quickly forking parent from producing children between child killing and parent killing
    for _child in $(ps -o pid --no-headers --ppid ${_pid}); do
        killtree ${_child} ${_sig}
    done
    kill -${_sig} ${_pid}
}

if [ $# -eq 0 -o $# -gt 2 ]; then
    echo "Usage: $(basename $0) <pid> [signal]"
    exit 1
fi

killtree $@

3
В --аргументах psне працюють на OS X. Щоб зробити його роботу там замінити psкоманду на: ps ax -o "pid= ppid=" | grep -E "${_regex}" | sed -E "s/${_regex}/\1/gде _regexвизначаються до forциклу:local _regex="[ ]*([0-9]+)[ ]+${_pid}"
Артуру

5
Зупинені процеси не вбиваються за допомогою SIGTERM. Дивіться мою відповідь
x-yuri

1
-1 використовує #! / Bin / bash замість #! / Usr / bin / env bash (а ще краще POSIX лише конструкції та / bin / sh)
Добрий чоловік

Чи достатньо буде надіслати SIGKILL замість SIGTERM, щоб гарантувати killtree()надійність роботи?
подарувати

3
якщо psне підтримується --ppid, можна скористатися pgrep -P ${_pid}натомість
Хуберт Каріо

16

RKill команди з PsList пакета посилає даний сигнал (абоSIGTERMза замовчуванням) для зазначеного процесу і всіх його нащадків:

rkill [-SIG] pid/name...

11

відповідь brad - це те, що я б рекомендував, за винятком того, що ви можете взагалі позбутися awk, якщо скористаєтесь --ppidопцією для ps.

for child in $(ps -o pid -ax --ppid $PPID) do ....... done

Це не працює для мене, якщо я чомусь не вийму -ax (Centos5). Інакше це чудово!
xitrium

9

якщо ви знаєте, передайте pid батьківського процесу, ось сценарій оболонки, який повинен працювати:

for child in $(ps -o pid,ppid -ax | \
   awk "{ if ( \$2 == $pid ) { print \$1 }}")
do
  echo "Killing child process $child because ppid = $pid"
  kill $child
done

Деякі версії ps накинуть попередження, якщо ви використовуєте "-ax" замість "ax". Таким чином: для дитини в $ (ps -o pid, ppid ax | \ awk "{if (\ $ 2 == $ pid) {print \ $ 1}}")
Cory R. King

9

Я використовую трохи модифіковану версію описаного тут методу: https://stackoverflow.com/a/5311362/563175

Так виглядає так:

kill `pstree -p 24901 | sed 's/(/\n(/g' | grep '(' | sed 's/(\(.*\)).*/\1/' | tr "\n" " "`

де 24901 - батьківський PID.

Це виглядає досить некрасиво, але це працює ідеально.


1
Спрощення з grep, замість sed ...pstree -p 24901 | grep -oP '(?<=\()[0-9]+(?=\))'
anishsane

2
ви повинні додати -lдо pstree, так довгі рядки не отримують усічені; це може бути спрощено читати також з kill `pstree -l -p 24901 |grep "([[:digit:]]*)" -o |tr -d '()'`(не потрібно перетворювати \nв простір, оскільки це буде добре працювати), thx!
Сила Водолія

9

Модифікована версія відповіді жигана:

#!/usr/bin/env bash
set -eu

killtree() {
    local pid
    for pid; do
        kill -stop $pid
        local cpid
        for cpid in $(pgrep -P $pid); do
            killtree $cpid
        done
        kill $pid
        kill -cont $pid
        wait $pid 2>/dev/null || true
   done
}

cpids() {
    local pid=$1 options=${2:-} space=${3:-}
    local cpid
    for cpid in $(pgrep -P $pid); do
        echo "$space$cpid"
        if [[ "${options/a/}" != "$options" ]]; then
            cpids $cpid "$options" "$space  "
        fi
    done
}

while true; do sleep 1; done &
cpid=$!
for i in $(seq 1 2); do
    cpids $$ a
    sleep 1
done
killtree $cpid
echo ---
cpids $$ a

ви можете wait $pidлише в запущених процесах, не у всіх прецесах, тому це не є загальним рішенням
Хуберт Каріо

@Hubert Kario У такому випадку чекання просто вийде з ненульовим статусом і продовжить виконання сценарію. Я помиляюся? Але waitпридушить Terminatedповідомлення, якщо це дитина.
x-yuri

9

Я не можу коментувати (недостатньо репутації), тому змушений додати нову відповідь , хоча це насправді не відповідь.

Існує невелика проблема з інакше дуже приємною і ґрунтовною відповіддю, наданою @olibre 28 лютого. Вихід ps opgid= $PIDбуде містити провідні пробіли для PID, менші ніж п’ять цифр, оскільки psце виправдовує стовпчик (строго вирівнювання чисел). У всьому командному рядку це призводить до від'ємного знаку, після чого пробілів, а потім групи PID. Просте рішення полягає в трубу , psщоб trдля видалення просторів:

kill -- -$( ps opgid= $PID | tr -d ' ' )

Дякую вам танажер Я
виправлю

8

Щоб додати відповідь Нормана Рамзі, можливо, варто поглянути на setid, якщо ви хочете створити групу процесів.
http://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.html

Функція setid () повинна створити новий сеанс, якщо процес виклику не є лідером групи процесів. Після повернення процес виклику повинен бути керівником сесії цієї нової сесії, бути керівником групи процесів нової групи процесів і не мати контрольного терміналу. Ідентифікатор групи процесу процесу виклику встановлюється рівним ідентифікатору процесу процесу виклику. Процес виклику є єдиним процесом у новій групі процесів і єдиним процесом у новому сеансі.

Я вважаю, що ви можете створити групу з початкового процесу. Я використовував це в php, щоб мати змогу вбити ціле дерево процесу після його запуску.

Це може бути поганою ідеєю. Мені були б цікаві коментарі.


Насправді це чудова ідея і працює дуже добре. Я використовую його в тих випадках, коли я можу розмістити процеси в одній групі процесів (або вони вже в одній групі).
хвороба

7

Натхненний коментарем ysth

kill -- -PGID

замість того, щоб давати йому номер процесу, надайте йому заперечення номера групи. Як зазвичай з майже будь-якою командою, якщо ви хочете, щоб звичайний аргумент, який починається з а, -не інтерпретувався як комутатор, передуйте йому--


На жаль, я щойно зрозумів, що я дав таку ж відповідь, що і ви => +1. Але крім того , я поясню , як просто отримати PGIDз PID. Як ти гадаєш? Ура
олібре

5

Наступна функція оболонки схожа на багато інших відповідей, але вона працює як на Linux, так і на BSD (OS X тощо) без зовнішніх залежностей, таких як pgrep:

killtree() {
    local parent=$1 child
    for child in $(ps -o ppid= -o pid= | awk "\$1==$parent {print \$2}"); do
        killtree $child
    done
    kill $parent
}

Я думаю, у вас є додаткове слово "дитина" в кінці 2-го рядка.
мато

@mato - Це не зайве. Це обмежує область дії $childцієї функції, щоб не турбувати інші (не локальні) змінні з тим самим іменем та забезпечити очищення значення локальної змінної після закінчення функції.
Адам Кац

О, я бачу зараз, я думав, що це частина завдання. Напевно, я повинен перечитати (повільніше), перш ніж писати коментар. :) Дякую.
мато

До речі, не було б краще створити список дітей, а потім почати вбивати зверху (батьків)? .. Таким чином, ми могли б уникнути ситуації, коли батьки відтворюють дитину або продовжують виконувати наступний код, який потенційно може змінити намічену поведінку.
мато

5

Це дуже просто зробити з python за допомогою psutil . Просто встановіть psutil з pip, і тоді у вас є повний набір інструментів для маніпулювання процесом:

def killChildren(pid):
    parent = psutil.Process(pid)
    for child in parent.get_children(True):
        if child.is_running():
            child.terminate()

5

Виходячи з відповіді жигана, це дозволяє уникнути самовбивства:

init_killtree() {
    local pid=$1 child

    for child in $(pgrep -P $pid); do
        init_killtree $child
    done
    [ $pid -ne $$ ] && kill -kill $pid
}


3

Це моя версія вбивства всіх дочірніх процесів за допомогою bash script. Він не використовує рекурсії і залежить від команди pgrep.

Використовуйте

killtree.sh PID SIGNAL

Зміст killtrees.sh

#!/bin/bash
PID=$1
if [ -z $PID ];
then
    echo "No pid specified"
fi

PPLIST=$PID
CHILD_LIST=`pgrep -P $PPLIST -d,`

while [ ! -z "$CHILD_LIST" ]
do
    PPLIST="$PPLIST,$CHILD_LIST"
    CHILD_LIST=`pgrep -P $CHILD_LIST -d,`
done

SIGNAL=$2

if [ -z $SIGNAL ]
then
    SIGNAL="TERM"
fi
#do substring from comma to space
kill -$SIGNAL ${PPLIST//,/ }

1
Це не вдасться, якщо після створення дочірнього списку будуть створені нові процеси.
припинення

3

Ось варіант відповіді @ zhigang, який обходиться без AWK, покладаючись лише на можливості рідного розбору Баша:

function killtree {
  kill -STOP "$1"
  ps -e -o pid= -o ppid= | while read -r pid ppid
                           do
                             [[ $ppid = $1 ]] || continue
                             killtree "$pid"  || true # Skip over failures
                           done
  kill -CONT "$1"          
  kill -TERM "$1"
}

Схоже, він працює як на Mac, так і на Linux. У ситуаціях, коли ви не можете розраховувати на можливість керувати групами процесів - як, наприклад, під час написання скриптів для тестування програмного забезпечення, яке повинно бути вбудовано в декількох середовищах - ця методика вигулу дерев, безумовно, корисна.


1

Дякую за вашу мудрість, люди. Мій сценарій залишав деякі дочірні процеси на виході, і підказка заперечення полегшила ситуацію. Я написав цю функцію для використання в інших сценаріях:

# kill my group's subprocesses:          killGroup
# kill also myself:                      killGroup -x
# kill another group's subprocesses:     killGroup N  
# kill that group all:                   killGroup -x N
# N: PID of the main process (= process group ID).

function killGroup () {
    local prid mainpid
    case $1 in
        -x) [ -n "$2" ] && kill -9 -$2 || kill -9 -$$ ;;
        "") mainpid=$$ ;;
         *) mainpid=$1 ;;
    esac
    prid=$(ps ax -o pid,pgid | grep $mainpid)
    prid=${prid//$mainpid/}
    kill -9 $prid 2>/dev/null
    return
}

Ура.


1

якщо у вашій системі є pstree та perl, ви можете спробувати це:

perl -e 'kill 9, (`pstree -p PID` =~ m/\((\d+)\)/sg)'

1

Напевно, краще вбити батька перед дітьми; інакше батько, можливо, знову нерестує нових дітей до того, як він сам буде вбитий. Вони переживуть вбивство.

Моя версія PS відрізняється від наведеної вище; можливо, занадто давній, тому дивне гарчання ...

Використання сценарію оболонки замість функції оболонки має багато переваг ...

Однак це в основному ідея жигангів


#!/bin/bash
if test $# -lt 1 ; then
    echo >&2 "usage: kiltree pid (sig)"
fi ;

_pid=$1
_sig=${2:-TERM}
_children=$(ps j | grep "^[ ]*${_pid} " | cut -c 7-11) ;
echo >&2 kill -${_sig} ${_pid}
kill -${_sig} ${_pid}
for _child in ${_children}; do
    killtree ${_child} ${_sig}
done

зауважте, що сценарій @zhighang SIGSTOP батьківський процес і подає сигнал до зупиненого процесу, тому це не повинно (AFAIK) спричиняти перегони між умовами створення дітей та доставкою сигналу. Ваша версія, хоч і перемагає між отриманням списку дітей та доставкою сигналу батькові.
Хуберт Каріо

1

Далі було протестовано на FreeBSD, Linux та MacOS X і залежить лише від pgrep та kill (версія PS -o не працює під BSD). Перший аргумент - батьківський під, від якого дітей потрібно припинити. Другий аргумент - булевий, щоб визначити, чи потрібно також скасувати батьківський під.

KillChilds() {
        local pid="${1}"
        local self="${2:-false}"

        if children="$(pgrep -P "$pid")"; then
                for child in $children; do
                        KillChilds "$child" true
                done
        fi

        if [ "$self" == true ]; then
                kill -s SIGTERM "$pid" || (sleep 10 && kill -9 "$pid" &)
        fi
}

KillChilds $$ > /dev/null 2>&1

Це надішле SIGTERM будь-якому процесу дитини / онука в рамках сценарію оболонки, і якщо SIGTERM не вдасться, він почекатиме 10 секунд, а потім відправить вбитий.


Рання відповідь:

Наступне також працює, але знищить саму оболонку на BSD.

KillSubTree() {
    local parent="${1}"
    for child in $(ps -o pid=$parent); do
            if [ $$ -ne $child ]; then (kill -s SIGTERM $child || (sleep 10 && kill -9 $child & )) > /dev/null 2>&1 ; fi
    done
}
# Example lanch from within script
KillSubTree $$ > /dev/null 2>&1

1

Я розробляю рішення жигангу, ксюрі та твердого тіла далі:

 #!/bin/bash

if test $# -lt 1 ; then
    echo >&2 "usage: kiltree pid (sig)"
    exit 1 ;
  fi ;

_pid=$1
_sig=${2:-TERM}

# echo >&2 "killtree($_pid) mypid = $$"
# ps axwwf | grep -6 "^[ ]*$_pid " >&2 ;

function _killtree () {
    local _children
    local _child
    local _success

    if test $1 -eq $2 ; then # this is killtree - don't commit suicide!
        echo >&2 "killtree can´t kill it´s own branch - some processes will survive." ; 
        return 1 ;
      fi ;
    # this avoids that children are spawned or disappear.
    kill -SIGSTOP $2 ;

    _children=$(ps -o pid --no-headers --ppid $2) ;        
    _success=0 
    for _child in ${_children}; do
        _killtree $1 ${_child} $3 ;
        _success=$(($_success+$?)) ;
      done ;

    if test $_success -eq 0 ; then
        kill -$3 $2
      fi ;
    # when a stopped process is killed, it will linger in the system until it is continued
    kill -SIGCONT $2
    test $_success -eq 0 ;
    return $?
    }

_killtree $$ $_pid $_sig

Ця версія дозволить уникнути вбивства свого роду, що спричиняє затоплення дитячих процесів у попередніх рішеннях.

Процеси належним чином зупиняються до того, як буде визначено дочірній список, щоб не створювалось і не зникало нових дітей.

Після вбивства припинені робочі місця мають продовжуватись зникати із системи.


1

Я знаю старе питання, але всі відповіді, здається, продовжують дзвонити на ps, що мені не сподобалось.

Це рішення, засноване на awk, не потребує рекурсії, а викликає ps лише один раз.

awk 'BEGIN {
  p=1390
  while ("ps -o ppid,pid"|getline) a[$1]=a[$1]" "$2
  o=1
  while (o==1) {
    o=0
    split(p, q, " ")
    for (i in q) if (a[q[i]]!="") {
      p=p""a[q[i]]
      o=1
      a[q[i]]=""
    }
  }
  system("kill -TERM "p)
}'

Або на однорядку:

awk 'BEGIN {p=1390;while ("ps -o ppid,pid"|getline) a[$1]=a[$1]" "$2;o=1;while (o==1) {o=0;split(p, q, " ");for (i in q) {if (a[q[i]]!="") {p=p""a[q[i]];o=1;a[q[i]]=""}}}system("kill -TERM "p)}'

В основному ідея полягає в тому, що ми збираємо батьківський масив (a): дочірні записи, а потім обводимо навколо масиву пошук дітей для наших відповідних батьків, додаючи їх до нашого списку батьків (p), коли ми йдемо.

Якщо ви не хочете вбивати процес вищого рівня, то робіть

sub(/[0-9]*/, "", p)

безпосередньо перед тим, як рядок system () видалить його з набору убивств.

Майте на увазі, що тут є умова гонки, але це правда (наскільки я бачу) усіх рішень. Він робить те, що мені потрібно, тому що сценарій, який мені потрібен, не створює багато короткочасних дітей.

Вправою для читача було б зробити це циклом з 2 пропусками: після першого проходу надішліть SIGSTOP до всіх процесів у списку p, потім цикл, щоб запустити ps ще раз, а після другого проходу надішліть SIGTERM, потім SIGCONT. Якщо вам не байдуже приємні закінчення, то, мабуть, другий пропуск може бути просто SIGKILL.


0

Якщо ви знаєте, що ви хочете вбити, ви можете перейти з ідентифікатора сеансу і все в той же сеанс. Я перевірив би двічі, але я використав це для скриптів, що починають rsyncs у циклах, які я хочу померти, а не запускати інший (через цикл), як це було б, якби я просто killall'd rsync.

kill $(ps -o pid= -s $(ps -o sess --no-heading --pid 21709))

Якщо ви не знаєте під, ви все ще можете гніздитися

kill $(ps -o pid= -s $(ps -o sess --no-heading --pid $(pgrep rsync )))


0

У команді "завдання" відображаються фонові процеси. У деяких випадках може бути краще спочатку вбити найновіший процес, наприклад, старший створив спільний сокет. У цих випадках сортуйте PID у зворотному порядку. Іноді потрібно зачекати момент, коли завдання щось записують на диск або подібні речі, перш ніж вони зупиняться.

І не вбивайте, якщо вам не доведеться!

for SIGNAL in TERM KILL; do
  for CHILD in $(jobs -s|sort -r); do
    kill -s $SIGNAL $CHILD
    sleep $MOMENT
  done
done

0

Вбивство дочірнього процесу в сценарії оболонки:

Багато часу нам потрібно вбити дитячий процес, якого чомусь повісили або перекрили. напр. Проблема з'єднання FTP.

Є два підходи,

1) Створити окремий новий батько для кожної дитини, який буде відслідковувати та вбивати процес дитини після досягнення тайм-ауту.

Створіть test.sh наступним чином,

#!/bin/bash

declare -a CMDs=("AAA" "BBB" "CCC" "DDD")
for CMD in ${CMDs[*]}; do
    (sleep 10 & PID=$!; echo "Started $CMD => $PID"; sleep 5; echo "Killing $CMD => $PID"; kill $PID; echo "$CMD Completed.") &
done
exit;

і спостерігати за процесами, які мають ім'я як "тест" в іншому терміналі, використовуючи наступну команду.

watch -n1 'ps x -o "%p %r %c" | grep "test" '

Вище написаний сценарій створить 4 нові дочірні процеси та їх батьків. Кожен дочірній процес триватиме 10 сек. Але як тільки час вичерпається в 5 сек, їхні батьківські процеси вбивають цих дітей. Тому дитина не зможе завершити виконання (10 сек). Пограйте в ці таймінги (перемикання 10 і 5), щоб побачити іншу поведінку. У такому випадку дитина закінчить виконання за 5 сек до того, як досягне тайм-ауту 10сек.

2) Нехай поточний батьківський моніторинг і вбиває процес дочіння після досягнення тайм-ауту. Це не створить окремого батька для моніторингу кожної дитини. Також ви можете керувати всіма дочірніми процесами належним чином у одному батьківському середовищі.

Створіть test.sh наступним чином,

#!/bin/bash

declare -A CPIDs;
declare -a CMDs=("AAA" "BBB" "CCC" "DDD")

CMD_TIME=15;
for CMD in ${CMDs[*]}; do
    (echo "Started..$CMD"; sleep $CMD_TIME; echo "$CMD Done";) &
    CPIDs[$!]="$RN";
    sleep 1;
done

GPID=$(ps -o pgid= $$);
CNT_TIME_OUT=10;
CNT=0;
while (true); do
    declare -A TMP_CPIDs;

    for PID in "${!CPIDs[@]}"; do
        echo "Checking "${CPIDs[$PID]}"=>"$PID;

        if ps -p $PID > /dev/null ; then
          echo "-->"${CPIDs[$PID]}"=>"$PID" is running..";
          TMP_CPIDs[$PID]=${CPIDs[$PID]};
        else
          echo "-->"${CPIDs[$PID]}"=>"$PID" is completed.";
        fi
    done

    if [ ${#TMP_CPIDs[@]} == 0 ]; then
        echo "All commands completed.";
        break;
    else
        unset CPIDs;
        declare -A CPIDs;
        for PID in "${!TMP_CPIDs[@]}"; do
            CPIDs[$PID]=${TMP_CPIDs[$PID]};
        done
        unset TMP_CPIDs;

        if [ $CNT -gt $CNT_TIME_OUT ]; then
            echo ${CPIDs[@]}"PIDs not reponding. Timeout reached $CNT sec. killing all childern with GPID $GPID..";
            kill -- -$GPID;
        fi
    fi

    CNT=$((CNT+1));
    echo "waiting since $b secs..";
    sleep 1;
done

exit;

і спостерігати за процесами, які мають ім'я як "тест" в іншому терміналі, використовуючи наступну команду.

watch -n1 'ps x -o "%p %r %c" | grep "test" '

Вище написаний сценарій створить 4 нові дочірні процеси. Ми зберігаємо підписи всіх дочірніх процесів і перебираємо їх на певне, щоб перевірити, чи завершено їх виконання чи все ще працює. Дочірній процес буде виконуватися до CMD_TIME часу. Але якщо час очікування закінчиться CNT_TIME_OUT, усі діти будуть вбиті батьківським процесом. Ви можете перемикати таймінг і грати зі сценарієм, щоб побачити поведінку. Одним із недоліків такого підходу є те, що він використовує груповий ідентифікатор для вбивства всього дитячого дерева. Але сам батьківський процес належить до однієї групи, тому він також загине.

Можливо, вам потрібно буде призначити інший ідентифікатор групи для батьківського процесу, якщо ви не хочете, щоб батьки були вбиті.

Більш детальну інформацію можна знайти тут,

Вбивство дочірнього процесу в сценарії оболонки


0

Цей сценарій також працює:

#/bin/sh while true do echo "Enter parent process id [type quit for exit]" read ppid if [ $ppid -eq "quit" -o $ppid -eq "QUIT" ];then exit 0 fi for i in `ps -ef| awk '$3 == '$ppid' { print $2 }'` do echo killing $i kill $i done done


0

Я знаю, що це старе, але це краще рішення, яке я знайшов:

killtree() { 
    for p in $(pstree -p $1 | grep -o "([[:digit:]]*)" |grep -o "[[:digit:]]*" | tac);do
        echo Terminating: $p 
        kill $p
    done
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.