Я хочу запустить фоновое задание на Python из bash script, а затем изящно убить его с помощью SIGINT. Это отлично работает с оболочкой, но я не могу заставить ее работать в script.
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
Но из script я не могу:
$ ./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
И если он был запущен из script, я больше не могу его убить из оболочки:
$ kill -SIGINT 15452
$ ps ax | grep loop.py | grep -v grep
15452 pts/3 R 0:34 python loop.py
Я предполагаю, что мне не хватает точной настройки управления работой bash.
Вы не регистрируете обработчик сигналов. Попробуйте ниже. Кажется, он работает достаточно надежно. Я думаю, что редким исключением является то, что он улавливает сигнал до того, как Python зарегистрирует обработчик script. Обратите внимание, что 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()
Я согласен с Мэтью Флашеном; проблема связана с python, который, по-видимому, не регистрирует исключение KeyboardInterrupt с SIGINT, когда он не вызывается из интерактивной оболочки.
Конечно, ничто не мешает вам регистрировать обработчик сигнала следующим образом:
def signal_handler(signum, frame):
raise KeyboardInterrupt, "Signal handler"
В дополнение к ответу @matthew-flaschen вы можете использовать exec
в bash script, чтобы эффективно заменить область на открываемый процесс:
#!/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
bash 4.3.48
на Ubuntu 16.04.3). Спасибо!
Когда вы запустите команду в фоновом режиме с помощью &, SIGINT будет проигнорирован. Здесь соответствующий раздел man bash:
Нестроенные команды, выполняемые bash, имеют обработчики сигналов, установленные для значений, унаследованных оболочкой от его родитель. Когда управление заданиями не действует, асинхронные команды игнорируют SIGINT и SIGQUIT в дополнение к этим унаследованным обработчикам. Команды, выполняемые в результате подстановки команд, игнорируют управляемые клавиатурой сигналы управления SIGTTIN, SIGTTOU и SIGTSTP.
Я думаю, вам нужно явно задать обработчик сигналов, как прокомментировал Матфей.
script 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()
18.8. signal — Set handlers for **asynchronous** events
Тот факт, что он работает для синхронной команды, не является случайностью.