Ответы:
Чтобы навести порядок, trap
можно использовать. Он может предоставить список вещей, выполняемых при поступлении определенного сигнала:
trap "echo hello" SIGINT
но также может использоваться для выполнения чего-либо, если оболочка завершает работу:
trap "killall background" EXIT
Это встроенный, поэтому help trap
даст вам информацию (работает с Bash). Если вы хотите убить только фоновые задания, вы можете сделать
trap 'kill $(jobs -p)' EXIT
Остерегайтесь использовать одиночный '
, чтобы предотвратить $()
немедленную замену оболочки .
kill $(jobs -p)
не работает в dash, потому что выполняет подстановку команд в подоболочке (см. Подстановка команд в man dash)
killall background
должно быть заполнителем? background
нет в
Это работает для меня (улучшилось благодаря комментаторам):
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
4.3.30(1)-release
OSX, и это также подтверждается на Ubuntu . Впрочем , есть и обвоиусный оборот :)
-$$
. Он оценивается как '- <PID> `например -1234
. На встроенной man-странице kill // главная черточка указывает сигнал для отправки. Однако - возможно, это блокирует, но в противном случае начальная черта не документирована. Любая помощь?
man 2 kill
, которая объясняет, что когда PID является отрицательным, сигнал отправляется всем процессам в группе процессов с предоставленным идентификатором ( en.wikipedia.org/wiki/Process_group ). Это сбивает с толку, что это не упоминается в man 1 kill
или man bash
, и может рассматриваться как ошибка в документации.
Обновление: https://stackoverflow.com/a/53714583/302079 улучшает это, добавляя статус выхода и функцию очистки.
trap "exit" INT TERM
trap "kill 0" EXIT
Зачем конвертировать INT
и TERM
выходить? Потому что оба должны вызывать kill 0
без входа в бесконечный цикл.
Почему триггер kill 0
на EXIT
? Потому что нормальные выходы скрипта kill 0
тоже должны срабатывать .
Почему kill 0
? Потому что вложенные вложенные оболочки также должны быть убиты. Это уничтожит все дерево процессов .
kill 0
означает / делает?
trap 'kill $ (jobs -p)' EXIT
Я бы внес лишь незначительные изменения в ответ Йоханнеса и использовал бы задания -pr, чтобы ограничить уничтожение запущенными процессами и добавить еще несколько сигналов в список:
trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT
trap 'kill 0' SIGINT SIGTERM EXIT
Решение , описанное в @ tokland отвечают очень приятно, но последняя Bash падает с ошибкой segmantation при ее использовании. Это потому, что Bash, начиная с v. 4.3, допускает рекурсию ловушек, которая становится бесконечной в этом случае:
SIGINT
или SIGTERM
или EXIT
;kill 0
, что отправляет SIGTERM
всем процессам в группе, включая саму оболочку;Это можно обойти, вручную отменив регистрацию ловушки:
trap 'trap - SIGTERM && kill 0' SIGINT SIGTERM EXIT
Более причудливый способ, который позволяет печатать полученный сигнал и избегает сообщений "Termination:":
#!/usr/bin/env bash
trap_with_arg() { # from https://stackoverflow.com/a/2183063/804678
local func="$1"; shift
for sig in "$@"; do
trap "$func $sig" "$sig"
done
}
stop() {
trap - SIGINT EXIT
printf '\n%s\n' "recieved $1, killing children"
kill -s SIGINT 0
}
trap_with_arg 'stop' EXIT SIGINT SIGTERM SIGHUP
{ i=0; while (( ++i )); do sleep 0.5 && echo "a: $i"; done } &
{ i=0; while (( ++i )); do sleep 0.6 && echo "b: $i"; done } &
while true; do read; done
UPD : добавлен минимальный пример; улучшена stop
функция, позволяющая избежать захвата ненужных сигналов и скрыть сообщения «Ter прекращено:» с выхода. Спасибо Тревору Бойду Смиту за предложения!
stop()
вас предоставить первый аргумент как номер сигнала , но тогда вы жёстко , что сигналы были сняты с регистрации. вместо жесткого кодирования отменяемых сигналов вы можете использовать первый аргумент для отмены регистрации в stop()
функции (это может привести к остановке других рекурсивных сигналов (кроме трех жестко закодированных)).
SIGINT
, но kill 0
отправляет SIGTERM
, что снова попадет в ловушку. Это не приведет к бесконечной рекурсии, потому что SIGTERM
будет захвачено во время второго stop
вызова.
trap - $1 && kill -s $1 0
должно работать лучше. Я проверю и обновлю этот ответ. Спасибо за хорошую идею! :)
trap - $1 && kill -s $1 0
тоже не получится, потому что мы не можем убить EXIT
. Но действительно достаточно сделать de-trap TERM
, потому что kill
посылает этот сигнал по умолчанию.
EXIT
, trap
обработчик сигнала всегда выполняется только один раз.
Чтобы быть в безопасности, я считаю, что лучше определить функцию очистки и вызвать ее из ловушки:
cleanup() {
local pids=$(jobs -pr)
[ -n "$pids" ] && kill $pids
}
trap "cleanup" INT QUIT TERM EXIT [...]
или вообще избегать функции:
trap '[ -n "$(jobs -pr)" ] && kill $(jobs -pr)' INT QUIT TERM EXIT [...]
Зачем? Потому что простое использование trap 'kill $(jobs -pr)' [...]
предполагает, что будут выполняться фоновые задания, когда сигнализируется условие прерывания. Когда нет рабочих мест, вы увидите следующее (или похожее) сообщение:
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]
потому что jobs -pr
пусто - я закончил в этой «ловушке» (каламбур).
[ -n "$(jobs -pr)" ]
не работает на моем Bash. Я использую GNU bash, версия 4.2.46 (2) -релиз (x86_64-redhat-linux-gnu). Сообщение «kill: использование» продолжает появляться.
jobs -pr
не возвращает PID дочерних элементов фоновых процессов. Он не разрушает все дерево процессов, а только обрезает корни.
Хорошая версия, которая работает под Linux, BSD и MacOS X. Сначала пытается отправить SIGTERM, и, если это не удается, убивает процесс через 10 секунд.
KillJobs() {
for job in $(jobs -p); do
kill -s SIGTERM $job > /dev/null 2>&1 || (sleep 10 && kill -9 $job > /dev/null 2>&1 &)
done
}
TrapQuit() {
# Whatever you need to clean here
KillJobs
}
trap TrapQuit EXIT
Обратите внимание, что работа не включает в себя процессы внучат
function cleanup_func {
sleep 0.5
echo cleanup
}
trap "exit \$exit_code" INT TERM
trap "exit_code=\$?; cleanup_func; kill 0" EXIT
# exit 1
# exit 0
Как https://stackoverflow.com/a/22644006/10082476 , но с добавленным кодом выхода
exit_code
приходят из в INT TERM
ловушку?
jobs -p не работает во всех оболочках, если вызывается в под-оболочке, возможно, если его вывод не перенаправлен в файл, но не в канал. (Я предполагаю, что первоначально он был предназначен только для интерактивного использования.)
Как насчет следующего:
trap 'while kill %% 2>/dev/null; do jobs > /dev/null; done' INT TERM EXIT [...]
Вызов «jobs» необходим в dash-оболочке Debian, которая не может обновить текущее задание («%%»), если оно отсутствует.
trap 'echo in trap; set -x; trap - TERM EXIT; while kill %% 2>/dev/null; do jobs > /dev/null; done; set +x' INT TERM EXIT; sleep 100 & while true; do printf .; sleep 1; done
Если вы запустите его в Bash (5.0.3) и попытаетесь завершиться, похоже, существует бесконечный цикл. Однако, если вы прекратите его снова, это работает. Даже с помощью Dash (0.5.10.2-6) вы должны завершить его дважды.
Я адаптировал ответ @ tokland в сочетании со знаниями из http://veithen.github.io/2014/11/16/sigterm-propagation.html, когда заметил, что trap
это не сработает, если я запускаю процесс переднего плана (без фона &
):
#!/bin/bash
# killable-shell.sh: Kills itself and all children (the whole process group) when killed.
# Adapted from http://stackoverflow.com/a/2173421 and http://veithen.github.io/2014/11/16/sigterm-propagation.html
# Note: Does not work (and cannot work) when the shell itself is killed with SIGKILL, for then the trap is not triggered.
trap "trap - SIGTERM && echo 'Caught SIGTERM, sending SIGTERM to process group' && kill -- -$$" SIGINT SIGTERM EXIT
echo $@
"$@" &
PID=$!
wait $PID
trap - SIGINT SIGTERM EXIT
wait $PID
Пример того, как это работает:
$ bash killable-shell.sh sleep 100
sleep 100
^Z
[1] + 31568 suspended bash killable-shell.sh sleep 100
$ ps aux | grep "sleep"
niklas 31568 0.0 0.0 19640 1440 pts/18 T 01:30 0:00 bash killable-shell.sh sleep 100
niklas 31569 0.0 0.0 14404 616 pts/18 T 01:30 0:00 sleep 100
niklas 31605 0.0 0.0 18956 936 pts/18 S+ 01:30 0:00 grep --color=auto sleep
$ bg
[1] + 31568 continued bash killable-shell.sh sleep 100
$ kill 31568
Caught SIGTERM, sending SIGTERM to process group
[1] + 31568 terminated bash killable-shell.sh sleep 100
$ ps aux | grep "sleep"
niklas 31717 0.0 0.0 18956 936 pts/18 S+ 01:31 0:00 grep --color=auto sleep
Просто для разнообразия я опубликую вариант https://stackoverflow.com/a/2173421/102484 , потому что это решение приводит к сообщению «Прекращено» в моей среде:
trap 'test -z "$intrap" && export intrap=1 && kill -- -$$' SIGINT SIGTERM EXIT