Я хочу завершить все дерево процессов. Как лучше всего это сделать, используя какие-либо распространенные языки сценариев? Ищу простое решение.
Ответ 1
Вы не сказали, является ли дерево, которое вы хотите уничтожить, отдельной группой процессов (это часто случается, если дерево является результатом форкинга от запуска сервера или командной строки оболочки). Вы можете обнаружить группы процессов с помощью GNU ps следующим образом:
ps x -o "%p %r %y %x %c "
Если вы хотите завершить группу процессов, просто используйте команду kill(1), но вместо номера процесса укажите отрицательный номер группы. Например, чтобы завершить все процессы в группе 5112, используйте kill -TERM -- -5112.
Ответ 2
Для завершения всех процессов, принадлежащих одному дереву процессов, используется идентификатор группы процессов ( 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)
Объяснение
kill -9 -"$PGID"=> Отправляет сигнал 9 ( KILL) всем дочерним и вложенным процессам...
PGID=$(ps opgid= "$PID")=> Используется для получения идентификатора группы процессов из любого идентификатора процесса в дереве, а не только из идентификатора родительского процесса . Вариант ps opgid= $PIDis ps -o pgid --no-headers $PID where pgid может быть заменен на pgrp.
Но:
Ps вставляет начальные пробелы, если PID меньше пяти цифр, и выравнивает по правому краю. Вы можете использовать:
PGID=$(ps opgid= "$PID" | tr -d ' ');Ps из OSX всегда печатает заголовок, поэтому:
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" # соответствует [CRTL+C] с клавиатуры
kill -QUIT -"$PGID" # соответствует [CRTL+\] с клавиатуры
kill -CONT -"$PGID" # перезапустить остановленный процесс (вышеуказанные сигналы не завершают его)
sleep 2 # подождите завершения процесса (потребуется больше времени)
kill -KILL -"$PGID" # kill -9, если он не перехватывает сигналы (или глючит)
Ограничение:
когда 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, ID процесса отличается от ID группы.
Я думаю, что kill -- -$(ps -o pgid= $PID | grep -o [0-9]*) - это лучший простой трюк для уничтожения всего дерева процессов при вызове из другого Group ID (другого дерева процессов).
Ответ 3
Чтобы завершить дерево процессов рекурсивно, используйте killtree():
#!/bin/bash
killtree() {
local _pid=$1
local _sig=${2:--TERM}
kill -stop ${_pid} # нужен для того, чтобы остановить быстрое форкирование родителя от запуске дочерних процессов между завершением порожденного дочернего и завершением родительского
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 $@
Ответ 4
Модифицированная версия предыдущего ответа:
#!/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
Linux