Как убить фоновые процессы/задания, когда моя оболочка script завершает работу?

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

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

Ответ 1

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

trap "echo hello" SIGINT

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

trap "killall background" EXIT

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

trap 'kill $(jobs -p)' EXIT

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

Ответ 2

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

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

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

Ответ 3

trap "exit" INT TERM
trap "kill 0" EXIT

Зачем конвертировать INT и TERM для выхода? Потому что оба должны запускать kill 0 без ввода бесконечного цикла.

Зачем запускать kill 0 на EXIT? Потому что нормальные выходы script также должны запускать kill 0.

Почему kill 0? Потому что вложенные подоболочки также должны быть убиты. Это приведет к всему дереву процессов.

Ответ 4

trap 'kill $(jobs -p)' EXIT

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

trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT

Ответ 5

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

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

Это можно обойти, вручную де-регистрируя ловушку:

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

Ответ 6

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

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 пуст - я закончил эту "ловушку" (каламбур).

Ответ 7

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

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

Ответ 8

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

Ответ 9

Итак, script загрузка script. Запустите команду killall (или что-то еще доступное в вашей ОС), которая выполняется, как только закончится script.

Ответ 10

jobs -p не работает во всех оболочках, если вызывается в под-оболочке, возможно, если его вывод не перенаправлен в файл, а не на канал. (Я предполагаю, что он изначально предназначался только для интерактивного использования.)

Как насчет следующего:

trap 'while kill %% 2>/dev/null; do jobs > /dev/null; done' INT TERM EXIT [...]

Призыв к "заданию" необходим с Debian-оболочкой Debian, которая не обновляет текущее задание ( "%%" ), если оно отсутствует.

Ответ 11

Я сделал адаптацию ответа @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

Ответ 12

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/..., но с добавленным кодом выхода