Відповіді:
Щоб прибрати деякий безлад, trap
можна використовувати. Він може надати список матеріалів, виконаних, коли надходить певний сигнал:
trap "echo hello" SIGINT
але також можна використовувати для виконання чого-небудь, якщо оболонка виходить:
trap "killall background" EXIT
Це вбудований, тому help trap
дасть вам інформацію (працює з bash). Якщо ви хочете лише вбити фонові завдання, ви можете це зробити
trap 'kill $(jobs -p)' EXIT
Слідкуйте за тим, щоб використовувати одиночний '
, щоб запобігти $()
негайному замінюванню оболонки .
kill $(jobs -p)
не працює в тирі, тому що він виконує підстановку команд у нижній частині (див. пункт Заміна команди в man dash)
killall background
повинно бути заповнювачем? background
немає на сторінці чоловіка ...
Це працює для мене (покращено завдяки коментаторам):
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
4.3.30(1)-release
OSX, і це також підтверджено на Ubuntu . Однак існує обвоєна думка :)
-$$
. Він оцінюється до '- <PID> `, наприклад -1234
. У manpage // kill // вбудований manpage провідний тире вказує сигнал, який потрібно надіслати. Однак - ймовірно, це блокує, але тоді провідна тире не документально підтверджена інакше. Будь-яка допомога?
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
означає / робить?
пастка 'kill $ (jobs -p)' EXIT
Я вніс лише незначні зміни у відповідь Йоханнеса та використання завдань -pr, щоб обмежити вбивство запущеними процесами та додати ще кілька сигналів до списку:
trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT
trap 'kill 0' SIGINT SIGTERM EXIT
Рішення , описане в @ tokland відповідають дуже приємно, але остання 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 "$@"; 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 та приховування повідомлень "Припинено:" з виводу. Дякую Тревору Бойду Сміту за пропозиції!
stop()
вас надати перший аргумент як номер сигналу , але тоді ви жорстко , що сигнали були зняті з реєстрації. замість жорсткого коду відписаних сигналів ви можете використовувати перший аргумент, щоб скасувати реєстрацію у stop()
функції (це може потенційно зупинити інші рекурсивні сигнали (крім 3 твердо кодованих)).
SIGINT
, але kill 0
надсилає SIGTERM
, який знову потрапить у пастку. Це не призведе до нескінченної рекурсії, оскільки SIGTERM
під час другого stop
дзвінка буде знято пастку .
trap - $1 && kill -s $1 0
має працювати краще. Я перевірю і оновлю цю відповідь. Дякую за гарну ідею! :)
trap - $1 && kill -s $1 0
теж не буде працювати, тому що ми не можемо вбити EXIT
. Але дійсно достатньо зробити де-пастку 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)" ]
не працює на моєму ударі. Я використовую GNU bash, версія 4.2.46 (2) -release (x86_64-redhat-linux-gnu). Повідомлення "убий: використання" продовжує з'являтися.
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 [...]
Виклик "робочих місць" необхідний із штриховою оболонкою 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) і намагаєтеся припинити, здається, нескінченний цикл. Однак якщо ви скасуєте його знову, він працює. Навіть за допомогою тире (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