Как лучше всего отправить сигнал всем членам группы процессов?
Я хочу убить целое дерево процессов. Каков наилучший способ сделать это с использованием любых распространенных языков сценариев? Я ищу простое решение.
34 ответа
Вы не говорите, является ли дерево, которое вы хотите убить, одной группой процессов. (Это часто имеет место, если дерево является результатом разветвления от запуска сервера или командной строки оболочки.) Вы можете обнаружить группы процессов, используя 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
из любого идентификатора процесса (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")
=> Получить идентификатор группы процессов из любого идентификатора процесса дерева, а не только идентификатора родителя процесса. Вариацияps opgid= $PID
являетсяps -o pgid --no-headers $PID
гдеpgid
можно заменить наpgrp
,
Но: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)
ограничение
- Как заметил davide и Hubert Kario, когда
kill
вызывается процессом, принадлежащим тому же дереву,kill
рискует убить себя, прежде чем прекратить уничтожение всего дерева. - Поэтому обязательно запустите команду, используя процесс с другим идентификатором группы процессов.
Длинная история
> 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
было достаточно. Но в случае, если процесс порождается в Makefile
идентификатор процесса отличается от идентификатора группы.
Я думаю kill -- -$(ps -o pgid= $PID | grep -o [0-9]*)
это лучший простой способ убить целое дерево процессов при вызове из другого идентификатора группы (другого дерева процессов).
pkill -TERM -P 27888
Это уничтожит все процессы, которые имеют родительский процесс с идентификатором 27888.
Или более надежный:
CPIDS=$(pgrep -P 27888); (sleep 33 && kill -KILL $CPIDS &); kill -TERM $CPIDS
который график убивает 33 секунды позже и вежливо попросить процессы прекратить.
Смотрите этот ответ для уничтожения всех потомков.
Чтобы рекурсивно убить дерево процессов, используйте 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 $@
Команда rkill из пакета pslist отправляет данный сигнал (или SIGTERM
по умолчанию) указанному процессу и всем его потомкам:
rkill [-SIG] pid/name...
Я использую немного модифицированную версию метода, описанного здесь: /questions/25889325/hodit-po-derevu-protsessov/25889338#25889338
Так это выглядит так:
kill `pstree -p 24901 | sed 's/(/\n(/g' | grep '(' | sed 's/(\(.*\)).*/\1/' | tr "\n" " "`
где 24901 - PID родителя.
Это выглядит довольно уродливо, но отлично справляется со своей задачей.
Модифицированная версия ответа Жиганга:
#!/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
Я не могу комментировать (недостаточно репутации), поэтому я вынужден добавить новый ответ, хотя это не совсем ответ.
Есть небольшая проблема с очень хорошим и подробным ответом, данным @olibre 28 февраля. ps opgid= $PID
будет содержать начальные пробелы для PID короче пяти цифр, потому что ps
Оправдывает колонку (ровно выровняйте цифры). Во всей командной строке это приводит к отрицательному знаку, за которым следуют пробелы, за которыми следует PID группы. Простое решение - труба ps
в tr
удалить пробелы:
kill -- -$( ps opgid= $PID | tr -d ' ' )
Ответ Брэда - то, что я бы тоже порекомендовал, за исключением того, что вы можете покончить с awk
в целом, если вы используете --ppid
возможность 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
Чтобы добавить ответ Нормана Рэмси, возможно, стоит взглянуть на setsid, если вы хотите создать группу процессов.
http://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.html
Функция setsid() должна создать новый сеанс, если вызывающий процесс не является лидером группы процессов. По возвращении вызывающий процесс должен быть лидером сеанса этого нового сеанса, должен быть лидером группы процессов новой группы процессов и не должен иметь управляющего терминала. Идентификатор группы процессов вызывающего процесса должен быть установлен равным идентификатору процесса вызывающего процесса. Вызывающий процесс должен быть единственным процессом в новой группе процессов и единственным процессом в новом сеансе.
Под этим я понимаю, что вы можете создать группу из начального процесса. Я использовал это в php, чтобы иметь возможность убить целое дерево процессов после его запуска.
Это может быть плохой идеей. Я был бы заинтересован в комментариях.
Вдохновленный комментариями YSTH
kill -- -PGID
вместо того, чтобы дать ему номер процесса, дайте ему отрицание номера группы. Как обычно, практически с любой командой, если вам нужен нормальный аргумент, начинающийся с
-
чтобы не быть интерпретированным как переключатель, перед ним--
Исходя из ответа Жиганга, это позволяет избежать самоубийств:
init_killtree() {
local pid=$1 child
for child in $(pgrep -P $pid); do
init_killtree $child
done
[ $pid -ne $$ ] && kill -kill $pid
}
Это очень легко сделать с помощью Python, используя psutil. Просто установите psutil с pip, и тогда у вас будет полный набор инструментов для управления процессами:
def killChildren(pid):
parent = psutil.Process(pid)
for child in parent.get_children(True):
if child.is_running():
child.terminate()
Следующая функция оболочки похожа на многие другие ответы, но она работает как в 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
}
Если вы хотите убить процесс по имени:
killall -9 -g someprocessname
или же
pgrep someprocessname | xargs pkill -9 -g
Вот вариант ответа @zhigang, который обходится без AWK, полагаясь только на собственные возможности парсинга Bash:
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. В ситуациях, когда вы не можете полагаться на способность управлять группами процессов - например, при написании сценариев для тестирования программного обеспечения, которое должно быть построено в нескольких средах - эта техника обхода дерева определенно полезна.
Это моя версия уничтожения всех дочерних процессов с помощью bash-скрипта. Он не использует рекурсию и зависит от команды 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//,/ }
Чтобы сразу убить всю группу процессов, как
^C
делает:
PID="$(pgrep -f unique_command_line_part)"
if [[ -n "$PID" ]]
then
PGID="$(ps --no-headers -p $PID -o pgid)"
kill -SIGINT -- -${PGID// /}
fi
Каждая строка объясняется в этом ответе
Вероятно, лучше убить родителя раньше детей; в противном случае родитель может снова породить новых детей, прежде чем его убьют. Это переживет убийство.
Моя версия 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
Я разрабатываю решение zhigang, xyuri и solidsneck дальше:
#!/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
Эта версия позволит избежать уничтожения своих предков - что вызывает поток дочерних процессов в предыдущих решениях.
Процессы должным образом останавливаются до определения списка дочерних элементов, поэтому новые дочерние элементы не создаются и не исчезают.
После уничтожения остановленные задания должны продолжать исчезать из системы.
Следующее было протестировано на FreeBSD, Linux и MacOS X и зависит только от pgrep и kill (версии ps -o не работают под BSD). Первый аргумент - родительский pid, дочерние элементы которого должны быть прекращены. Второй аргумент - логическое значение, определяющее, должен ли родительский pid также быть завершен.
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 секунд, а затем отправит kill.
Предыдущий ответ:
Следующее также работает, но уничтожит саму оболочку на 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
Старый вопрос, я знаю, но все ответы, кажется, продолжают вызывать пс, что мне не понравилось.
Это решение на основе 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) записей parent:child, а затем зацикливаемся вокруг массива, находя дочерних элементов для наших подходящих родителей, добавляя их в список наших родителей (p) по мере продвижения.
Если вы не хотите убивать процесс верхнего уровня, тогда
sub(/[0-9]*/, "", p)
незадолго до того, как строка system() удалит его из списка уничтожений.
Имейте в виду, что здесь есть условие гонки, но это верно (насколько я вижу) для всех решений. Он делает то, что мне нужно, потому что сценарий, для которого он мне нужен, не создает много недолговечных детей.
Упражнение для читателя состояло бы в том, чтобы сделать его двухпроходным циклом: после первого прохода отправьте SIGSTOP всем процессам в списке p, затем выполните цикл для повторного запуска ps, а после второго прохода отправьте SIGTERM, затем SIGCONT. Если вы не заботитесь о хороших концовках, то вторым проходом может быть просто SIGKILL, я полагаю.
Спасибо за вашу мудрость, ребята. Мой сценарий оставлял некоторые дочерние процессы при выходе, а подсказка отрицания упростила ситуацию. Я написал эту функцию для использования в других сценариях, если это необходимо:
# 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
}
Приветствия.
Я знаю, что это старо, но это лучшее решение, которое я нашел:
killtree() {
for p in $(pstree -p $1 | grep -o "([[:digit:]]*)" |grep -o "[[:digit:]]*" | tac);do
echo Terminating: $p
kill $p
done
}
Если у вас есть pstree и perl в вашей системе, вы можете попробовать это:
perl -e 'kill 9, (`pstree -p PID` =~ m/\((\d+)\)/sg)'
В sh команда jobs перечислит фоновые процессы. В некоторых случаях может быть лучше сначала убить самый новый процесс, например, более старый создал общий сокет. В этих случаях сортируйте PID в обратном порядке. Иногда вам нужно подождать, пока задания запишут что-то на диск или что-то подобное, прежде чем они остановятся.
И не убивай, если не надо!
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;
и наблюдайте за процессами, которые имеют имя "test" в другом терминале, используя следующую команду.
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;
и наблюдайте за процессами, которые имеют имя "test" в другом терминале, используя следующую команду.
watch -n1 'ps x -o "%p %r %c" | grep "test" '
Выше скрипт создаст 4 новых дочерних процесса. Мы храним pids всех дочерних процессов и зацикливаем их, чтобы проверить, закончили ли они свое выполнение или все еще работают. Дочерний процесс будет выполняться до времени CMD_TIME. Но если тайм-аут CNT_TIME_OUT достигнут, все дочерние процессы будут уничтожены родительским процессом. Вы можете переключать время и играть со скриптом, чтобы увидеть поведение. Недостатком этого подхода является использование идентификатора группы для уничтожения всего дочернего дерева. Но сам родительский процесс принадлежит к той же группе, поэтому он также будет уничтожен.
Вам может потребоваться назначить другой идентификатор группы родительскому процессу, если вы не хотите, чтобы родительский процесс был убит.
Более подробную информацию можно найти здесь,
А теперь немного об умном программировании оболочки.
У этого решения есть затраты, но, по крайней мере, оно основано на ежедневных итерациях и рекурсиях. Его можно преобразовать в bash, уделив особое вниманиеtypeset
команды и преобразование их в declare
или local
где это уместно.
Обсуждение
Убивая процесс, нужно иметь дело с реальностью, что он может быть родителем для многих потомков, и что каждый потомок может быть родителем для еще большего количества потомков, и так далее, и так далее.
Что делать?
Если бы только была функция для проверки наличия у процесса дочерних процессов и другая функция для возврата дочерних PID родительского процесса.
Тогда игра была бы намного проще, потому что вы могли бы создать цикл для итерации по списку PID, проверяя каждый из них на наличие потомков, прежде чем убивать его. Если детей нет, просто убейте процесс. Если есть дочерние элементы, вызовите функцию управления рекурсивно и передайте ей результаты функции, которая получает PID дочерних элементов родителя.
Действие базового случая (процесс не имеет потомков).
#!/bin/ksh
function killProcess ()
{
typeset -r PID=$1
if [[ ! isProcess $PID ]]
then
echo -e "Process $PID cannot be terminated because it does not exist.\n" 1>&2
return 1
elif [[ kill -s TERM $PID ]] && [[ ! isProcess $PID ]]
then
echo -e "Process $PID was terminated.\n" 1>&2
return 0
elif kill -s KILL $PID
echo -e "Process $PID killed with SIGKILL (9) signal. No time to clean up potential files.\n" 1>&2
return 0
elif isZombie $PID
then
echo -e "Process $PID in the zombie status.\n" 1>&2
return 2
else
echo -e "Process $PID is alive. SIGTERM and SIGKILL had no effect. It is not a zombie.\n" 1>&2
fi
return 3
}
function attemptToKillPid ()
{
typeset -r PID=$1
if killProcess $PID
then
return 0
fi
ppid=$(getParentPid $pid)
echo -e "Process $pid of parent $ppid was not able to be killed.\n" 1>&2
return 1
}
Общее действие случая (у процесса есть потомки).
function killPidFamily ()
{
typeset -r PROCESSES=$*
typeset -ir NUM_PROCESSES_TO_KILL=$(countLines $PROCESSES)
typeset -i numKilledProcesses=0
typeset ppid
for pid in $PROCESSES
do
pid=$(trim $pid)
if ! hasChildPids $pid
then
attemptToKillPid $pid && (( numKilledProcesses++ ))
else
killPidFamily $(getChildPids $pid) && attemptToKillPid $pid && (( numKilledProcesses++ ))
fi
done
(( numKilledProcesses == NUM_PROCESSES_TO_KILL ))
return $?
}
Библиотека вспомогательных функций.
#!/bin/ksh
function trim ()
{
echo -n "$1" | tr -d [:space:]
}
function countLines ()
{
typeset -r $LIST=$*
trim $(echo $LIST | wc -l | awk {'print $1'})
}
function getProcesses ()
{
# NOTE: -o pgid below would be $4 in awk.
ps -e -o comm,pid,ppid,pgid,user,ruid,euid,group,rgid,egid,etime,etimes,stat --no-headers
}
function getProcess ()
{
typeset -r PID=$1
ps -p $PID -o comm,pid,ppid,pgid,user,ruid,euid,group,rgid,egid,etime,etimes,stat --no-headers
}
function isProcess ()
{
typeset -r PID=$1
ps -p $PID -o pid --no-headers 1>&2
return $?
}
function getProcessStatus ()
{
typeset -r PID=$1
trim $(ps -p $PID -o stat --no-headers)
}
function isZombie ()
{
typeset -r PID=$1
typeset processStatus
processStatus=$(getProcessStatus $PID)
[[ "$processStatus" == "Z" ]]
return $?
}
function hasChildPids ()
{
typeset -r PPID=$1
echo $(getProcesses) | awk '{print $3}' | sort -n | uniq | grep "^${PPID}$"
return $?
}
function getChildPids ()
{
typeset -r PPID=$1
echo $(getProcesses) | awk '{print $2, $3}' | sort -k 2 | awk "\$2 == $PPID {print \$1}" | sort -n
}
function getParentPid ()
{
typeset -r PID=$1
trim $(echo $(getProcess $PID) | awk '{print $3}')
}
Таким образом, вы точно знаете, что дерево процессов разрушается от ветвей, продвигаясь к корню. Это помогает избежать возможности создания зомби и других нежелательных ситуаций.
Теперь, когда вы увидели наиболее затратный способ сделать это (уничтожение одного процесса за раз), исследуйте, как вы можете изменить это решение, чтобы использовать PGID (идентификатор группы процессов). ВgetProcesses ()
функция уже печатает PGID ($4
в awk
), так что узнайте, как его использовать, или нет.