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

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

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

190 shell
11.12.2008 17:33:00
13 ОТВЕТОВ
РЕШЕНИЕ

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

trap "echo hello" SIGINT

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

trap "killall background" EXIT

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

trap 'kill $(jobs -p)' EXIT

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

181
12.12.2008 16:33:04
тогда как ты убиваешь только ребенка ? (или я что-то
elmarco 11.12.2008 20:51:01
killall убивает ваших детей, но не вас
orip 11.12.2008 22:10:56
kill $(jobs -p)не работает в dash, потому что выполняет подстановку команд в подоболочке (см. Подстановка команд в man dash)
user1431317 15.06.2017 13:37:13
это killall backgroundдолжно быть заполнителем? backgroundнет в
Evan Benn 11.07.2019 04:45:04

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

0
11.12.2008 17:48:03

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

1
11.12.2008 22:15:57

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

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

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

161
19.02.2015 19:44:46
В целом должно работать хорошо, но дочерние процессы могут менять группы процессов. С другой стороны, он не требует контроля работы и может также пропустить некоторые процессы внука, которые пропустят другие решения.
michaeljt 13.12.2012 09:17:03
Обратите внимание: «kill 0» также убивает родительский скрипт bash. Вы можете использовать «kill - - $ BASHPID» для уничтожения только дочерних элементов текущего сценария. Если у вас нет BASHPID в вашей bash-версии, вы можете экспортировать BASHPID = $ (sh -c 'echo $ PPID')
ACyclic 23.08.2013 16:35:24
Спасибо за приятное и понятное решение! К сожалению, это segfaults Bash 4.3, который допускает рекурсию ловушек. Я столкнулся с этим на 4.3.30(1)-releaseOSX, и это также подтверждается на Ubuntu . Впрочем , есть и обвоиусный оборот :)
skozin 5.02.2015 00:31:00
Я не совсем понимаю -$$. Он оценивается как '- <PID> `например -1234. На встроенной man-странице kill // главная черта указывает сигнал для отправки. Однако - возможно, это блокирует, но в противном случае начальная черта не документирована. Любая помощь?
Evan Benn 11.07.2019 04:48:18
@EvanBenn: проверка man 2 kill, которая объясняет, что когда PID является отрицательным, сигнал отправляется всем процессам в группе процессов с предоставленным идентификатором ( en.wikipedia.org/wiki/Process_group ). Это сбивает с толку, что это не упоминается в man 1 killили man bash, и может рассматриваться как ошибка в документации.
user001 1.09.2019 22:32:52

trap 'kill $ (jobs -p)' EXIT

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

trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT
22
8.04.2011 02:03:11

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

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

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

Вызов «jobs» необходим в dash-оболочке Debian, которая не может обновить текущее задание («%%»), если оно отсутствует.

0
13.12.2012 09:13:00

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

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

8
15.06.2013 03:51:21
Этот тест [ -n "$(jobs -pr)" ]не работает на моем Bash. Я использую GNU bash, версия 4.2.46 (2) -релиз (x86_64-redhat-linux-gnu). Сообщение «kill: using» продолжает появляться.
Douwe van der Leest 27.05.2019 06:52:15
Я подозреваю, что это связано с тем, что jobs -prне возвращает PID дочерних элементов фоновых процессов. Он не разрушает все дерево процессов, а только обрезает корни.
Douwe van der Leest 27.05.2019 07:06:12

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

109
22.02.2019 14:44:11
Единственное решение для моего случая на Debian.
MindlessRanger 6.06.2015 17:39:13
Ни ответ Йоханнеса Шауба, ни ответ, предоставленный Tokland, не смогли уничтожить фоновые процессы, запущенные моим сценарием оболочки (в Debian). Это решение сработало. Я не знаю, почему за этот ответ не проголосовали больше. Не могли бы вы подробнее рассказать о том, что именно kill 0означает / делает?
josch 24.11.2016 15:09:40
@josch, если вы еще не узнали, вот объяснениеkill 0
sanmai 9.01.2017 02:39:17
Это потрясающе, но также убивает мою родительскую оболочку :-(
vidstige 7.03.2017 13:31:00
Это решение буквально излишне. kill 0 (внутри моего скрипта) разрушил всю мою сессию X! Возможно, в некоторых случаях kill 0 может быть полезным, но это не меняет того факта, что оно не является общим решением, и его следует избегать, если это возможно, если только нет очень веской причины его использовать. Было бы неплохо добавить предупреждение о том, что оно может уничтожить родительскую оболочку или даже весь сеанс X, а не только фоновые задания скрипта!
Lissanro Rayen 2.06.2017 14:20:55

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

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

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

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функция, позволяющая избежать захвата ненужных сигналов и скрыть сообщения «Termination:» с выхода. Спасибо Тревору Бойду Смиту за предложения!

14
23.05.2017 12:10:26
в stop()вас предоставить первый аргумент как номер сигнала , но тогда вы жёстко , что сигналы были сняты с регистрации. вместо жесткого кодирования отменяемых сигналов вы можете использовать первый аргумент для отмены регистрации в stop()функции (это может привести к остановке других рекурсивных сигналов (кроме трех жестко закодированных)).
Trevor Boyd Smith 16.02.2015 15:28:20
@TrevorBoydSmith, я думаю, это не сработает. Например, оболочка может быть уничтожена SIGINT, но kill 0отправляет SIGTERM, что снова попадет в ловушку. Это не приведет к бесконечной рекурсии, потому что SIGTERMбудет захвачено во время второго stopвызова.
skozin 16.02.2015 17:48:30
Наверное, trap - $1 && kill -s $1 0должно работать лучше. Я проверю и обновлю этот ответ. Спасибо за хорошую идею! :)
skozin 16.02.2015 17:50:05
Нет, trap - $1 && kill -s $1 0тоже не получится, потому что мы не можем убить EXIT. Но действительно достаточно сделать de-trap TERM, потому что killпосылает этот сигнал по умолчанию.
skozin 16.02.2015 18:10:10
Я проверял рекурсию с EXIT, trapобработчик сигнала всегда выполняется только один раз.
Trevor Boyd Smith 16.02.2015 18:18:03

Я адаптировал ответ @ 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
0
2.07.2015 13:21:56

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

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

2
14.09.2015 21:01:41
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 , но с добавленным кодом выхода

2
10.12.2018 22:23:27

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

trap 'test -z "$intrap" && export intrap=1 && kill -- -$$' SIGINT SIGTERM EXIT
0
8.02.2019 13:30:49