Как отправить SIGINT на Python из bash-скрипта?
Я хочу запустить фоновое задание Python из bash-скрипта, а затем аккуратно убить его с помощью SIGINT. Это прекрасно работает из оболочки, но я не могу заставить его работать в сценарии.
loop.py:
#! /usr/bin/env python
if __name__ == "__main__":
try:
print 'starting loop'
while True:
pass
except KeyboardInterrupt:
print 'quitting loop'
Из оболочки я могу его прервать:
$ python loop.py &
[1] 15420
starting loop
$ kill -SIGINT 15420
quitting loop
[1]+ Done python loop.py
kill.sh:
#! /bin/bash
python loop.py &
PID=$!
echo "sending SIGINT to process $PID"
kill -SIGINT $PID
Но из сценария я не могу:
$ ./kill.sh
starting loop
sending SIGINT to process 15452
$ ps ax | grep loop.py | grep -v grep
15452 pts/3 R 0:08 python loop.py
И, если он был запущен из скрипта, я больше не могу убить его из оболочки:
$ kill -SIGINT 15452
$ ps ax | grep loop.py | grep -v grep
15452 pts/3 R 0:34 python loop.py
Я предполагаю, что мне не хватает какой-то тонкой точки контроля работы Bash.
5 ответов
Вы не регистрируете обработчик сигнала. Попробуйте ниже. Кажется, работает достаточно надежно. Я думаю, что редкое исключение - когда он ловит сигнал, прежде чем Python регистрирует обработчик скрипта. Обратите внимание, что KeyboardInterrupt должен вызываться только "когда пользователь нажимает клавишу прерывания". Я думаю, что тот факт, что он работает для явного (например, через kill) SIGINT, является случайностью реализации.
import signal
def quit_gracefully(*args):
print 'quitting loop'
exit(0);
if __name__ == "__main__":
signal.signal(signal.SIGINT, quit_gracefully)
try:
print 'starting loop'
while True:
pass
except KeyboardInterrupt:
quit_gracefully()
В дополнение к ответу @matthew-flaschen вы можете использовать exec
в скрипте bash для эффективной замены области видимости открываемого процесса:
#!/bin/bash
exec python loop.py &
PID=$!
sleep 5 # waiting for the python process to come up
echo "sending SIGINT to process $PID"
kill -SIGINT $PID
Я согласен с Мэтью Флашеном; проблема в python, который, очевидно, не регистрирует исключение KeyboardInterrupt в SIGINT, когда он не вызывается из интерактивной оболочки.
Конечно, ничто не мешает вам зарегистрировать ваш обработчик сигналов следующим образом:
def signal_handler(signum, frame):
raise KeyboardInterrupt, "Signal handler"
Когда вы запускаете команду в фоновом режиме с &, SIGINT будет игнорироваться. Вот соответствующий раздел man bash:
У не встроенных команд, запускаемых bash, в обработчиках сигналов установлены значения, унаследованные оболочкой от ее родителя. Когда управление заданиями не действует, асинхронные команды игнорируют SIGINT и SIGQUIT в дополнение к этим унаследованным обработчикам. Команды, выполняемые в результате подстановки команд, игнорируют генерируемые клавиатурой сигналы управления заданиями SIGTTIN, SIGTTOU и SIGTSTP.
Я думаю, вам нужно явно установить обработчик сигнала, как прокомментировал Мэтью.
В скрипте kill.sh тоже есть проблема. Поскольку loop.py отправляется в фоновый режим, нет гарантии, что kill выполняется после python loop.py.
#! /bin/bash
python loop.py &
PID=$!
#
# NEED TO WAIT ON EXISTENCE OF python loop.py PROCESS HERE.
#
echo "sending SIGINT to process $PID"
kill -SIGINT $PID
Пробовал подход @Steen, но, увы, он явно не держится на Mac.
Другое решение, почти то же самое, что и выше, но немного более общее, - просто переустановить обработчик по умолчанию, если SIGINT
игнорируется:
def _ensure_sigint_handler():
# On Mac, even using `exec <cmd>` in `bash` still yields an ignored SIGINT.
sig = signal.getsignal(signal.SIGINT)
if signal.getsignal(signal.SIGINT) == signal.SIG_IGN:
signal.signal(signal.SIGINT, signal.default_int_handler)
# ...
_ensure_sigint_handler()