Как убить фоновые процессы / задания при выходе из моего сценария оболочки? - PullRequest
163 голосов
/ 11 декабря 2008

Я ищу способ убрать беспорядок, когда мой скрипт верхнего уровня завершается.

Особенно, если я хочу использовать set -e, я бы хотел, чтобы фоновый процесс умер при выходе из скрипта.

Ответы [ 13 ]

160 голосов
/ 11 декабря 2008

Чтобы убрать беспорядок, можно использовать trap. Он может предоставить список вещей, выполняемых при поступлении определенного сигнала:

trap "echo hello" SIGINT

но также может использоваться для выполнения чего-либо, если оболочка завершается:

trap "killall background" EXIT

Это встроенная функция, поэтому help trap даст вам информацию (работает с bash). Если вы хотите убить только фоновые задания, вы можете сделать

trap 'kill $(jobs -p)' EXIT

Остерегайтесь использовать одиночный ', чтобы предотвратить немедленную замену оболочки $().

143 голосов
/ 01 февраля 2010

Это работает для меня (улучшено благодаря комментаторам):

trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
  • kill -- -$$ отправляет SIGTERM всей группе процессов, таким образом убивая также потомков.

  • Задание сигнала EXIT полезно при использовании set -e (подробнее здесь ).

99 голосов
/ 25 марта 2014

Обновление: 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? Потому что вложенные вложенные оболочки также должны быть убиты. Это уничтожит все дерево процессов .

21 голосов
/ 07 апреля 2011

trap 'kill $ (jobs -p)' EXIT

Я бы внес лишь незначительные изменения в ответ Йоханнеса и использовал бы задания -pr, чтобы ограничить уничтожение запущенными процессами и добавить еще несколько сигналов в список:

trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT
12 голосов
/ 05 февраля 2015

Решение trap 'kill 0' SIGINT SIGTERM EXIT, описанное в ответе @ tokland , действительно хорошо, но последний Bash вылетает с ошибкой сегментации при его использовании. Это потому, что Bash, начиная с v. 4.3, допускает рекурсию ловушек, которая в этом случае становится бесконечной:

  1. процесс оболочки получает SIGINT или SIGTERM или EXIT;
  2. сигнал попадает в ловушку, выполняя kill 0, который отправляет SIGTERM всем процессам в группе, включая саму оболочку;
  3. перейти к 1:)

Эту проблему можно обойти, вручную отменив регистрацию ловушки:

trap 'trap - SIGTERM && kill 0' SIGINT SIGTERM EXIT

Более причудливый способ, позволяющий распечатывать полученный сигнал и избегающий сообщений "Ter прекращено:":

#!/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 прекращено:» с выхода. Спасибо Тревор Бойд Смит за предложения!

7 голосов
/ 15 июня 2013

Чтобы быть в безопасности, я считаю, что лучше определить функцию очистки и вызвать ее из ловушки:

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 пусто - я попал в эту «ловушку» (каламбур).

2 голосов
/ 15 сентября 2015

Хорошая версия, которая работает под 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

Обратите внимание, что задания не включают процессы потомков.

1 голос
/ 11 декабря 2018
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,, но с добавленным кодом выхода

1 голос
/ 12 декабря 2008

Другой вариант - установить скрипт в качестве лидера группы процессов и перехватить killpg в вашей группе процессов при выходе.

0 голосов
/ 08 февраля 2019

Просто для разнообразия я опубликую вариант https://stackoverflow.com/a/2173421/102484, потому что это решение приводит к сообщению "Прекращено" в моей среде:

trap 'test -z "$intrap" && export intrap=1 && kill -- -$$' SIGINT SIGTERM EXIT
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...