Каков наилучший способ отправить сигнал всем членам группы процессов?
Я хочу убить целое дерево процессов. Каков наилучший способ сделать это с помощью любых распространенных языков сценариев? Я ищу простое решение.
30 ответов:
вы не говорите, что дерево, которое вы хотите убить, является одной группой процессов. (Это часто бывает, если дерево является результатом разветвления от запуска сервера или командной строки оболочки.) Вы можете обнаружить группы процессов с помощью GNU ps следующим образом:
ps x -o "%p %r %y %x %c "
Если это группа процессов, которую вы хотите убить, просто используйте
kill(1)
команда, но вместо того, чтобы дать ему номер процесса, дайте ему отрицание номер группы. Например, чтобы убить любой процесс в группе 5112, используйтеkill -TERM -- -5112
.
убить все процессы, принадлежащие к одному и тому же дерево процессов С помощью ID группы процессов (
PGID
)
kill -- -$PGID
использовать сигнал по умолчанию (TERM
= 15)kill -9 -$PGID
использовать сигналKILL
(9)вы можете получить
PGID
любой Process-ID (PID
) из той же )" ./child.sh background & ./child.sh foreground echo "ProcessID=$$ ends ()" > cat child.sh #!/bin/sh echo "ProcessID=$$ begins ()" ./grandchild.sh background & ./grandchild.sh foreground echo "ProcessID=$$ ends ()" > cat grandchild.sh #!/bin/sh echo "ProcessID=$$ begins ()" sleep 9999 echo "ProcessID=$$ ends ()"запустите дерево процессов в фоновом режиме с помощью '&'
> ./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
было достаточно. Но в случае, если процесс нерестится в пределахMakefile
the идентификатор процесса отличается от ID группы.я думаю
kill -- -$(ps -o pgid= $PID | grep -o [0-9]*)
лучший простой трюк, чтобы убить все дерево процесса при вызове из другой ID группы (другое дерево процессов).
pkill -TERM -P 27888
Это убьет все процессы, которые имеют идентификатор родительского процесса 27888.
или более надежные:
CPIDS=$(pgrep -P 27888); (sleep 33 && kill -KILL $CPIDS &); kill -TERM $CPIDS
который график убийства 33 секунд спустя и вежливо попросить процессы прекратить.
посмотреть ответ для прекращения всех потомков.
чтобы рекурсивно убить дерево процессов, используйте killtree ():
#!/bin/bash killtree() { local _pid= 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 ) <pid> [signal]" exit 1 fi killtree $@
бред-это то, что я бы тоже рекомендовал, за исключением того, что вы можете покончить с
awk
в целом, если вы используете доps
.for child in $(ps -o pid -ax --ppid $PPID) do ....... done
Если вы знаете, передайте 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
Я использую немного измененную версию метода, описанного здесь: https://stackoverflow.com/a/5311362/563175
Так выглядит так:
kill `pstree -p 24901 | sed 's/(/\n(/g' | grep '(' | sed 's/(\(.*\)).*//' | tr "\n" " "`
где 24901-ПИД родителя.
это выглядит довольно уродливо, но делает это работа отлично.
измененная версия ответа Жигана:
#!/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= 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
чтобы добавить к ответу Нормана Рэмси, возможно, стоит посмотреть на setsid, если вы хотите создать процесс group.
http://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.htmlфункция setsid() должна создать новый сеанс, если вызывающий процесс не лидер группы процессов. На возврат вызывающий процесс должен быть руководитель сессии этого нового сессия, будет группа процесса лидером новой группы процессов , и не имеет управляющего терминала. Идентификатор группы процессов вызова процесс должен быть установлен равным идентификатор вызывающего процесса. Этот вызывающий процесс должен быть единственным процесса в группе процессов и единственный процесс в новой сессии.
что я понимаю, что вы можете создать группу из начального процесса. Я использовал это в php, чтобы иметь возможность убить целое дерево процессов после его запуска.
Это может быть плохо идея. Я был бы заинтересован в комментариях.
Я не могу комментировать (не хватает репутации), поэтому я вынужден добавить новый ответ, хотя это не совсем ответ.
есть небольшая проблема с другим очень хорошим и тщательным ответом, данным @olibre 28 февраля. Выход
ps opgid= $PID
будет содержать ведущие пробелы для PID короче пяти цифр, потому чтоps
оправдывает столбец (rigth выравнивает числа). В пределах всей командной строки это приводит к отрицательному знаку, за которым следует пробел(ы), за которым следует группа PID. Простое решение-трубаps
доtr
для удаления пробелов:kill -- -$( ps opgid= $PID | tr -d ' ' )
вдохновленный комментарий ysth
kill -- -PGID
вместо того, чтобы дать ему номер процесса, дайте ему отрицание группы число. Как обычно с почти любой командой, если вы хотите нормальный аргумент, который начинается с
-
чтобы не интерпретироваться как переключатель, предшествуйте ему с--
следующая функция оболочки похожа на многие другие ответы, но она работает как на Linux, так и на BSD (OS X и т. д.) без внешних зависимостей, таких как
pgrep
:killtree() { local parent= child for child in $(ps -o ppid= -o pid= | awk "$1==$parent {print $2}"); do killtree $child done kill $parent }
это очень легко сделать с помощью python с помощью psutil. Просто установите psutil с pip, а затем у вас есть полный набор инструментов управления процессом:
def killChildren(pid): parent = psutil.Process(pid) for child in parent.get_children(True): if child.is_running(): child.terminate()
основываясь на ответе Жигана, это позволяет избежать самоуничтожения:
init_killtree() { local pid= child for child in $(pgrep -P $pid); do init_killtree $child done [ $pid -ne $$ ] && kill -kill $pid }
Если вы хотите убить процесс по имени:
killall -9 -g someprocessname
или
pgrep someprocessname | xargs pkill -9 -g
Это моя версия убийства всех дочерних процессов с помощью сценария bash. Он не использует рекурсию и зависит от команды pgrep.
использовать
killtree.sh PID SIGNAL
содержание killtrees.sh
#!/bin/bash PID= 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= if [ -z $SIGNAL ] then SIGNAL="TERM" fi #do substring from comma to space kill -$SIGNAL ${PPLIST//,/ }
вот вариант ответа @zhigang, который делает без AWK, полагаясь только на собственные возможности синтаксического анализа Bash:
function killtree { kill -STOP "" ps -e -o pid= -o ppid= | while read -r pid ppid do [[ $ppid = ]] || continue killtree "$pid" || true # Skip over failures done kill -CONT "" kill -TERM "" }
похоже, он отлично работает как на Mac, так и на Linux. В ситуациях, когда вы не можете полагаться на возможность управлять группами процессов-например, при написании сценариев для тестирования программного обеспечения, которое должно быть построено в нескольких средах-этот метод ходьбы по дереву определенно полезен.
вероятно, лучше убить родителя до детей; в противном случае родитель может, вероятно, породить новых детей снова, прежде чем он убьет себя. Они переживут убийство.
моя версия ps отличается от приведенной выше; возможно, слишком стара, поэтому странная grepping...
использование сценария оболочки вместо функции оболочки имеет много преимуществ...
однако, это в основном идея Жиганов
#!/bin/bash if test $# -lt 1 ; then echo >&2 "usage: kiltree pid (sig)" fi ; _pid= _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
следующее было протестировано на FreeBSD, Linux и MacOS X и зависит только от pgrep и kill (версии ps-o не работают под BSD). Первый аргумент-это родительский pid, дочерние элементы которого должны быть завершены. второй аргумент является логическим, чтобы определить, должен ли Родительский pid быть также завершен.
KillChilds() { local pid="" 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="" 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
Я разрабатываю решение zhigang, xyuri и solidsneck дальше:
#!/bin/bash if test $# -lt 1 ; then echo >&2 "usage: kiltree pid (sig)" exit 1 ; fi ; _pid= _sig=${2:-TERM} # echo >&2 "killtree($_pid) mypid = $$" # ps axwwf | grep -6 "^[ ]*$_pid " >&2 ; function _killtree () { local _children local _child local _success if test -eq ; 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 ; _children=$(ps -o pid --no-headers --ppid ) ; _success=0 for _child in ${_children}; do _killtree ${_child} ; _success=$(($_success+$?)) ; done ; if test $_success -eq 0 ; then kill - fi ; # when a stopped process is killed, it will linger in the system until it is continued kill -SIGCONT test $_success -eq 0 ; return $? } _killtree $$ $_pid $_sig
эта версия позволит избежать убийства своей родословной-что вызывает поток дочерних процессов в предыдущих решениях.
процессы должным образом останавливаются до определения дочернего списка, так что новые дочерние элементы не создаются и не исчезают.
после уничтожения остановленные задания должны продолжать исчезать из системы.
старый вопрос, я знаю, но все ответы, похоже, продолжают называть ps, что мне не понравилось.
Это решение на основе awk не требует рекурсии и вызывает ps только один раз.
awk 'BEGIN { p=1390 while ("ps -o ppid,pid"|getline) a[]=a[]" " 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[]=a[]" ";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, я полагаю.
Я знаю, что это старый, но это лучшее решение, которое я нашел:
killtree() { for p in $(pstree -p | grep -o "([[:digit:]]*)" |grep -o "[[:digit:]]*" | tac);do echo Terminating: $p kill $p done }
Спасибо за вашу мудрость, люди. Мой скрипт оставлял некоторые дочерние процессы на выходе и отрицание совет сделал вещи легче. Я написал эту функцию для использования в других скриптах, если это необходимо:
# 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 in -x) [ -n "" ] && kill -9 - || kill -9 -$$ ;; "") mainpid=$$ ;; *) mainpid= ;; esac prid=$(ps ax -o pid,pgid | grep $mainpid) prid=${prid//$mainpid/} kill -9 $prid 2>/dev/null return }
Ура.
если у вас есть pstree и perl в вашей системе, вы можете попробовать это:
perl -e 'kill 9, (`pstree -p PID` =~ m/\((\d+)\)/sg)'
Если вы знаете pid вещи, которую вы хотите убить, вы обычно можете перейти от идентификатора сеанса, и все в том же сеансе. Я бы дважды проверил, но я использовал это для сценариев запуска rsyncs в циклах, которые я хочу умереть, а не запускать другой (из-за цикла), как это было бы, если бы я просто убил 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 )))
в sh команда jobs выведет список фоновых процессов. В некоторых случаях может быть лучше сначала убить самый новый процесс, например, более старый создал общий сокет. В этих случаях сортируйте PIDs в обратном порядке. Иногда вы хотите дождаться момента, когда задания запишут что-то на диск или что-то подобное, прежде чем они остановятся.
и не убивайте, если вам это не нужно!
for SIGNAL in TERM KILL; do for CHILD in $(jobs -s|sort -r); do kill -s $SIGNAL $CHILD sleep $MOMENT done done
убийство дочернего процесса в сценарии оболочки:
много раз нам нужно убить дочерний процесс, который повешен или блокируется по какой-то причине. например. Проблема с подключением 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 сек, их соответствующие родительские процессы убьют этих детей. Таким образом, ребенок не сможет завершить выполнение(10sec). Играть вокруг этих таймингов(переключатель 10 и 5), чтобы увидеть другое поведение. В этом случае ребенок завершит выполнение в 5sec, прежде чем он достигнет таймаута 10sec.
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 новых дочерних процессов. Мы храним pids всех дочерних процессов и перебираем их, чтобы проверить, если они закончили свое исполнение или все еще работают. Дочерний процесс будет выполняться до времени CMD_TIME. Но если время ожидания CNT_TIME_OUT достигнет, все дети будут убиты родительским процессом. Вы можете переключать время и играть со скриптом, чтобы увидеть поведение. Одним из недостатков этого подхода является использование идентификатора группы для уничтожения всего дочернего дерева. Но сам родительский процесс принадлежит к той же группе, поэтому он также будет убит.
необходимо назначить другой ID группы родителя, если не хочу, чтобы родителя убили.
более подробную информацию можно найти здесь