Як я вбиваю фонові процеси / завдання, коли мій скрипт оболонки закінчується?


194

Я шукаю спосіб очистити безлад, коли мій сценарій вищого рівня закінчується.

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

Відповіді:


186

Щоб прибрати деякий безлад, trapможна використовувати. Він може надати список матеріалів, виконаних, коли надходить певний сигнал:

trap "echo hello" SIGINT

але також можна використовувати для виконання чого-небудь, якщо оболонка виходить:

trap "killall background" EXIT

Це вбудований, тому help trapдасть вам інформацію (працює з bash). Якщо ви хочете лише вбити фонові завдання, ви можете це зробити

trap 'kill $(jobs -p)' EXIT

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


то як ти вбиваєш дитину тільки? (або я пропускаю щось очевидне)
elmarco

18
killall вбиває ваших дітей, але не ви
відкрийте

4
kill $(jobs -p)не працює в тирі, тому що він виконує підстановку команд у нижній частині (див. пункт Заміна команди в man dash)
користувач1431317

8
це killall backgroundповинно бути заповнювачем? backgroundнемає на сторінці чоловіка ...
Еван Бенн

170

Це працює для мене (покращено завдяки коментаторам):

trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
  • kill -- -$$посилає SIGTERM до всієї групи процесів, тим самим вбиваючи також нащадків.

  • Вказівка ​​сигналу EXITкорисна при використанні set -e(детальніше тут ).


1
Потрібно добре працювати в цілому, але дочірні процеси можуть змінювати групи процесів. З іншого боку, він не потребує контролю за роботою, а також може отримати деякі онукові процеси, пропущені іншими рішеннями.
michaeljt

5
Зауважте, що "kill 0" також вбиває батьківський скрипт bash. Ви можете використовувати "kill - - $ BASHPID", щоб убити лише дітей поточного сценарію. Якщо у вашій баш-версії немає $ BASHPID, ви можете експортувати BASHPID = $ (sh -c 'echo $ PPID')
ACyclic

2
Дякую за приємне та чітке рішення! На жаль, він налаштовує Bash 4.3, що дозволяє реперувати пастку. Я наткнувся на це на 4.3.30(1)-releaseOSX, і це також підтверджено на Ubuntu . Однак існує обвоєна думка :)
skozin

4
Я не зовсім розумію -$$. Він оцінюється до '- <PID> `, наприклад -1234. У manpage // kill // вбудований manpage провідний тире вказує сигнал, який потрібно надіслати. Однак - ймовірно, це блокує, але тоді провідна тире не документально підтверджена інакше. Будь-яка допомога?
Еван Бенн

4
@EvanBenn: Перевірте man 2 kill, що пояснює, що коли PID є негативним, сигнал надсилається всім процесам у групі процесів із наданим ідентифікатором ( en.wikipedia.org/wiki/Process_group ). Заплутано те, що це не згадується в man 1 killабо man bash, і може вважатися помилкою в документації.
user001

111

Оновлення: 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? Тому що вкладені підшари також потрібно вбити. Це зніме все дерево процесу .


3
Єдине рішення для мого випадку на Debian.
MindlessRanger

3
Ні відповідь Йоханнеса Шауба, ні відповідь, яку надав tokland, не вдалося вбити фонові процеси, які почав мій сценарій оболонки (на Debian). Це рішення спрацювало. Я не знаю, чому ця відповідь не є більш прихильною. Чи можете ви розширити докладніше про те, що саме kill 0означає / робить?
Джош

7
Це дивовижно, але також вбиває мою батьківську оболонку :-(
vidstige

5
Це рішення є буквально непосильним. kill 0 (всередині мого сценарію) зруйнував весь мій X сеанс! Можливо, в деяких випадках kill 0 може бути корисним, але це не змінює того факту, що це не загальне рішення, і його слід уникати, якщо можливо, якщо немає дуже вагомих причин його використовувати. Було б непогано додати попередження про те, що це може вбити батьківську оболонку або навіть цілий X сеанс, а не лише фонові завдання сценарію!
Ліссанро Рейен

3
Хоча це може бути цікавим рішенням за певних обставин, на що вказував @vidstige, це знищить всю групу процесів, яка включає процес запуску (тобто батьківська оболонка в більшості випадків). Однозначно не те, що потрібно, коли ви запускаєте сценарій через IDE.
дозрівати

21

пастка 'kill $ (jobs -p)' EXIT

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

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

Чому б і не вбити зупинених робочих місць? У Bash EXIT пастка також буде запущена у випадку SIGINT та SIGTERM, тому пастка буде викликана двічі у випадку такого сигналу.
jarno

14

trap 'kill 0' SIGINT SIGTERM EXITРішення , описане в @ tokland відповідають дуже приємно, але остання Bash падає з помилкою segmantation при її використанні. Це тому, що Bash, починаючи з т. 4.3, дозволяє здійснювати пастку рекурсії, яка стає нескінченною в цьому випадку:

  1. процес отримання оболонки SIGINTабо SIGTERMабо EXIT;
  2. сигнал потрапляє в пастку, виконуючи kill 0, який надсилає SIGTERMвсі процеси в групі, включаючи саму оболонку;
  3. перейти до 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 "$@"; 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 твердо кодованих)).
Тревор Бойд Сміт

@TrevorBoydSmith, мабуть, це не працюватиме так, як очікувалося. Наприклад, оболонка може бути вбита за допомогою SIGINT, але kill 0надсилає SIGTERM, який знову потрапить у пастку. Це не призведе до нескінченної рекурсії, оскільки SIGTERMпід час другого stopдзвінка буде знято пастку .
skozin

Напевно, trap - $1 && kill -s $1 0має працювати краще. Я перевірю і оновлю цю відповідь. Дякую за гарну ідею! :)
skozin

Ні, trap - $1 && kill -s $1 0теж не буде працювати, тому що ми не можемо вбити EXIT. Але дійсно достатньо зробити де-пастку TERM, тому що killцей сигнал передається за замовчуванням.
Скозин

Я тестував рекурсію за допомогою EXIT, trapобробник сигналу завжди виконується лише один раз.
Тревор Бойд Сміт

9

Щоб бути на безпеці, я вважаю, що краще визначити функцію очищення та викликати її з пастки:

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 дітей фонових процесів. Це не руйнує все технологічне дерево, а лише обрізає коріння.
Дуве ван дер

2

Хороша версія, яка працює під 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

Інший варіант полягає в тому, щоб сценарій встановив себе як керівник групи процесів, а на виході потрапив пастку на файл вбивці у вашій групі процесів.


Як ви визначаєте процес як керівника групи процесів? Що таке "killpg"?
jarno

0

Тому сценарій завантаження сценарію. Запустіть killallкоманду (або все, що є у вашій ОС), яка виконується, як тільки сценарій буде закінчений.


0

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

Як щодо наступного:

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

Виклик "робочих місць" необхідний із штриховою оболонкою Debian, яка не може оновити поточне завдання ("%%"), якщо воно відсутнє.


Хм цікавий підхід, але, схоже, це не працює. Поміркуйте scipt 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) вам доведеться припинити його двічі.
jarno

0

Я зробив адаптацію відповіді @ 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

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

trap 'test -z "$intrap" && export intrap=1 && kill -- -$$' SIGINT SIGTERM EXIT
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.