Відповіді:
Щоб прибрати деякий безлад, 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)-releaseOSX, і це також підтверджено на 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