Я ищу способ очистить беспорядок, когда выйдет мой верхний уровень script.
В частности, если я хочу использовать set -e
, мне бы хотелось, чтобы фоновый процесс погиб, когда выйдет script.
Я ищу способ очистить беспорядок, когда выйдет мой верхний уровень script.
В частности, если я хочу использовать set -e
, мне бы хотелось, чтобы фоновый процесс погиб, когда выйдет script.
Чтобы очистить беспорядок, можно использовать trap
. Он может предоставить список вещей, выполняемых при получении определенного сигнала:
trap "echo hello" SIGINT
но также можно использовать для выполнения чего-либо, если оболочка завершена:
trap "killall background" EXIT
Он встроен, поэтому help trap
предоставит вам информацию (работает с bash). Если вы хотите убить фоновые задания, вы можете сделать
trap 'kill $(jobs -p)' EXIT
Остерегайтесь использования одиночного '
, чтобы предотвратить немедленную замену оболочки $()
.
Это работает для меня (улучшено благодаря комментаторам):
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
trap "exit" INT TERM
trap "kill 0" EXIT
Зачем конвертировать INT
и TERM
для выхода? Потому что оба должны запускать kill 0
без ввода бесконечного цикла.
Зачем запускать kill 0
на EXIT
? Потому что нормальные выходы script также должны запускать kill 0
.
Почему kill 0
? Потому что вложенные подоболочки также должны быть убиты. Это приведет к всему дереву процессов.
trap 'kill $(jobs -p)' EXIT
Я бы сделал лишь незначительные изменения в ответе Йоханнеса и использовал задания -pr, чтобы ограничить выполнение запущенных процессов и добавить еще несколько сигналов в список:
trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT
Решение trap 'kill 0' SIGINT SIGTERM EXIT
, описанное в @tokland answer, действительно красивое, но последнее Bash при его использовании происходит сбой с ошибкой segmantation. Это потому, что Bash, начиная с версии 4.3, разрешает рекурсию ловушки, которая в этом случае становится бесконечной:
SIGINT
или SIGTERM
или EXIT
;kill 0
, который отправляет SIGTERM
всем процессам в группе, включая собственно оболочку;Это можно обойти, вручную де-регистрируя ловушку:
trap 'trap - SIGTERM && kill 0' SIGINT SIGTERM EXIT
Более привлекательный способ, позволяющий печатать полученный сигнал и избегать сообщений "Завершение:":
#!/usr/bin/env bash
trap_with_arg() { # from https://stackoverflow.com/a/2183063/804678
local func="$1"; shift
for sig in "[email protected]"; 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
, чтобы aviod отключил ненужные сигналы и спрятал сообщения "Terminated:" с выхода. Спасибо Тревор Бойд Смит за предложения!
Чтобы быть в безопасности, мне лучше определить функцию очистки и вызвать ее из ловушки:
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
пуст - я закончил эту "ловушку" (каламбур).
Хорошая версия, которая работает под 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
Обратите внимание, что задания не включают процессы больших детей.
Другой вариант - установить script как лидер группы процессов и заблокировать killpg в вашей группе процессов при выходе.
Итак, script загрузка script. Запустите команду killall
(или что-то еще доступное в вашей ОС), которая выполняется, как только закончится script.
jobs -p не работает во всех оболочках, если вызывается в под-оболочке, возможно, если его вывод не перенаправлен в файл, а не на канал. (Я предполагаю, что он изначально предназначался только для интерактивного использования.)
Как насчет следующего:
trap 'while kill %% 2>/dev/null; do jobs > /dev/null; done' INT TERM EXIT [...]
Призыв к "заданию" необходим с Debian-оболочкой Debian, которая не обновляет текущее задание ( "%%" ), если оно отсутствует.
Я сделал адаптацию ответа @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 [email protected]
"[email protected]" &
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
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
Как fooobar.com/info/48894/..., но с добавленным кодом выхода