Как отправить SIGINT в Python из скрипта bash?

8

Я хочу запустить фоновое задание на 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.

Теги:

5 ответов

15
Лучший ответ

Вы не регистрируете обработчик сигналов. Попробуйте ниже. Кажется, он работает достаточно надежно. Я думаю, что редким исключением является то, что он улавливает сигнал до того, как 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()
  • 0
    Смотрите заголовок 18.8. signal — Set handlers for **asynchronous** events Тот факт, что он работает для синхронной команды, не является случайностью.
4

Я согласен с Мэтью Флашеном; проблема связана с python, который, по-видимому, не регистрирует исключение KeyboardInterrupt с SIGINT, когда он не вызывается из интерактивной оболочки.

Конечно, ничто не мешает вам регистрировать обработчик сигнала следующим образом:

def signal_handler(signum, frame):
    raise KeyboardInterrupt, "Signal handler"
2

В дополнение к ответу @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
  • 0
    Это кажется самым простым и сработало для меня ( bash 4.3.48 на Ubuntu 16.04.3). Спасибо!
  • 0
    Похоже, что это не так на Mac. Опубликуем продолжение.
2

Когда вы запустите команду в фоновом режиме с помощью &, 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
1

Пробовал подход @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()

Ещё вопросы

Сообщество Overcoder
Наверх
Меню