Переслати SIGTERM дитині в Bash


86

У мене є сценарій Bash, який схожий на це:

#!/bin/bash
echo "Doing some initial work....";
/bin/start/main/server --nodaemon

Тепер, якщо оболонка bash, що працює під сценарієм, отримує сигнал SIGTERM, вона також повинна відправити SIGTERM на запущений сервер (який блокує, тому пастка неможлива). Це можливо?

Відповіді:


91

Спробуйте:

#!/bin/bash 

_term() { 
  echo "Caught SIGTERM signal!" 
  kill -TERM "$child" 2>/dev/null
}

trap _term SIGTERM

echo "Doing some initial work...";
/bin/start/main/server --nodaemon &

child=$! 
wait "$child"

Зазвичай bashпід час виконання дочірнього процесу ігноруються будь-які сигнали. Якщо запустити сервер &, викликте його в систему управління роботою оболонки, $!утримуючи PID сервера (для використання з waitі kill). waitПотім дзвінок чекатиме завершення завдання із вказаним PID (сервером) або для отримання будь-яких сигналів .

Коли оболонка отримає SIGTERM(або сервер завершиться незалежно), waitвиклик повернеться (виходить із кодом виходу сервера або з номером сигналу + 128 у випадку отримання сигналу). Згодом, якщо оболонка отримала SIGTERM, вона викличе _termфункцію, вказану як обробник пастки SIGTERM, перед тим, як вийти (у якій ми робимо будь-яку очистку і передаємо сигнал вручну на серверний процес за допомогою kill).


Виглядає добре! Я спробую це і відповім, коли тестував.
Лоренц

7
Але exec замінює оболонку заданою програмою , мені не зрозуміло, для чого waitпотрібен наступний виклик?
iruvar

5
Я думаю, що точка 1_CR є дійсною. Або ви просто використовуєте exec /bin/start/main/server --nodaemon(у такому випадку процес оболонки замінюється на серверний процес, і вам не потрібно поширювати жодні сигнали), або ви використовуєте /bin/start/main/server --nodaemon &, але execце не дуже важливо.
Андреас Вейтен

2
Якщо ви хочете, щоб ваш скрипт оболонки закінчувався лише після того, як дитина припиняється, тоді у _term()функції ви повинні wait "$child"знову. Це може знадобитися, якщо у вас є якийсь інший наглядовий процес, який очікує, що скрипт оболонки загине, перш ніж його перезапустити, або якщо ви також захопили EXITзробити чистку та запустили його лише після закінчення дочірнього процесу.
LeoRochael

1
@AlexanderMills Прочитайте інші відповіді. Або ви шукаєте exec, або хочете встановити пастки .
Стюарт П. Бентлі

78

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

#!/bin/bash
echo "Doing some initial work....";
exec /bin/start/main/server --nodaemon

Якщо вам потрібно зберегти оболонку навколо якої - то причини (тобто. Ви повинні зробити деякі очищення після завершує сервер), ви повинні використовувати комбінацію trap, waitі kill. Дивіться відповідь SensorSmith .


Це правильна відповідь! Настільки більш лаконічні та адреси оригіналу запитують точно
BrDaHa

20

Андреас Вейтен зазначає, що якщо вам не потрібно повертатися з виклику (як у прикладі ОП), execдостатньо просто зателефонувати через команду (відповідь @Stuart P. Bentley ). Інакше "традиційний" trap 'kill $CHILDPID' TERM(відповідь @ cuonglm) - це початок, але waitвиклик насправді повертається після запуску обробника пастки, який все ще може бути до завершення дочірнього процесу. Тому бажаний "додатковий" дзвінок wait(відповідь @ користувача1463361 ).

Хоча це вдосконалення, він все ще має стан гонки, що означає, що процес може ніколи не виходити (якщо тільки сигналізатор не намагається відправити сигнал TERM). Вікно вразливості полягає між реєстрацією обробника пастки та записом PID дитини.

Далі усувається ця вразливість (упакована у функції для повторного використання).

prep_term()
{
    unset term_child_pid
    unset term_kill_needed
    trap 'handle_term' TERM INT
}

handle_term()
{
    if [ "${term_child_pid}" ]; then
        kill -TERM "${term_child_pid}" 2>/dev/null
    else
        term_kill_needed="yes"
    fi
}

wait_term()
{
    term_child_pid=$!
    if [ "${term_kill_needed}" ]; then
        kill -TERM "${term_child_pid}" 2>/dev/null 
    fi
    wait ${term_child_pid}
    trap - TERM INT
    wait ${term_child_pid}
}

# EXAMPLE USAGE
prep_term
/bin/something &
wait_term

2
Відмінна робота - я оновив посилання у своїй відповіді, щоб вказати тут (крім цього, це більш комплексне рішення, я все ще трохи вражений тим, що інтерфейс StackExchange не присвоює мені відповідь cuonglm за виправлення сценарію на насправді робити те, що належить, і написати майже весь пояснювальний текст після ОП, який навіть не зрозумів, зробив кілька незначних змін.
Стюарт П. Бентлі

2
@ StuartP.Bentley, спасибі Я був здивований тим, що зібрав це потрібні два (не прийняті) відповіді та зовнішнє посилання, і тоді мені довелося бігти за умови гонки. Я буду модернізувати свої посилання на посилання як на те, що я можу дати трохи додаткових кудо.
SensorSmith

3

Зазначене рішення не працює для мене, оскільки процес був убитий до того, як команда очікування фактично закінчилася. Я виявив, що стаття http://veithen.github.io/2014/11/16/sigterm-propagation.html , останній фрагмент добре працює в моєму випадку подання заявки в OpenShift із користувальницьким бігуном sh. Скрипт sh потрібен, оскільки мені потрібно мати можливість отримувати скиди ниток, що неможливо у випадку, якщо PID процесу Java дорівнює 1.

trap 'kill -TERM $PID' TERM INT
$JAVA_EXECUTABLE $JAVA_ARGS &
PID=$!
wait $PID
trap - TERM INT
wait $PID
EXIT_STATUS=$?
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.