Отправьте Ctrl-C в удаленные процессы, запущенные через subprocess.Popen и ssh

Как отправить Ctrl-C в несколько процессов ssh -t в Popen() объекты?

У меня есть код Python, который запускает script на удаленном хосте:

# kickoff.py

# i call 'ssh' w/ the '-t' flag so that when i press 'ctrl-c', it get's
# sent to the script on the remote host.  otherwise 'ctrol-c' would just
# kill things on this end, and the script would still be running on the
# remote server
a = subprocess.Popen(['ssh', '-t', 'remote-host', './script.sh', 'a'])
a.communicate()

Это отлично работает, но мне нужно запустить несколько сценариев на удаленном хосте:

# kickoff.py

a = subprocess.Popen(['ssh', '-t', 'remote-host', './script.sh', 'a'])
b = subprocess.Popen(['ssh', '-t', 'remote-host', './script.sh', 'b'])
a.communicate()
b.communicate()

Результатом этого является то, что Ctrl-C не надежно убивает все, и мой терминал всегда становится искаженным впоследствии (мне нужно запустить 'reset'). Итак, как я могу убить оба удаленных сценария, когда главный убит?

Примечание. Я пытаюсь избежать входа в удаленный хост, ища "script.sh" в списке процессов и отправляя SIGINT для обоих процессов. Я просто хочу, чтобы можно было нажать Ctrl-C на старте script и убить оба удаленных процесса. Менее оптимальное решение может включать детерминированный поиск PID удаленных сценариев, но я не знаю, как это сделать в моей текущей настройке.

Обновление: script, который запускается на удаленном сервере, фактически запускает несколько дочерних процессов, и, убивая ssh, он убивает оригинальный удаленный script (возможно, b/c SIGHUP), дети задачи не убиты.

Ответ 1

Единственный способ, которым я смог успешно убить все мои дочерние процессы, - это pexpect:

a = pexpect.spawn(['ssh', 'remote-host', './script.sh', 'a'])
a.expect('something')

b = pexpect.spawn(['ssh', 'remote-host', './script.sh', 'b'])
b.expect('something else')

# ...

# to kill ALL of the children
a.sendcontrol('c')
a.close()

b.sendcontrol('c')
b.close()

Это достаточно надёжно. Я считаю, что кто-то еще отправил этот ответ раньше, но затем удалил ответ, поэтому я отправлю его на случай, если кому-то интереснее.

Ответ 2

Когда он был убит, ssh отправит SIGHUP в удаленные процессы. Вы можете обернуть удаленные процессы в оболочку или python script, которые убьют их, когда этот script получит SIGHUP (см. Команду trap для bash и сигнального модуля в python)

Возможно, это возможно даже с раздутой командной строкой вместо удаленной обертки script.

Проблема заключается в том, что убить удаленные процессы не то, что вы хотите, что вы хотите, чтобы иметь рабочий терминал после Ctrl + C. для этого вам придется убить удаленные процессы и посмотреть оставшийся вывод, который будет содержать некоторые терминальные управляющие последовательности до reset терминала в правильном состоянии. Для этого вам понадобится mecanism, чтобы сообщить обертку script, чтобы убить процессы. Это не одно и то же.

Ответ 3

Я не пробовал это, но, возможно, вы можете поймать KeyboardInterrupt и затем убить процессы:

try
    a = subprocess.Popen(['ssh', '-t', 'remote-host', './script.sh', 'a'])
    b = subprocess.Popen(['ssh', '-t', 'remote-host', './script.sh', 'b'])
    a.communicate()
    b.communicate()
except KeyboardInterrupt:
    os.kill(a.pid, signal.SIGTERM)
    os.kill(b.pid, signal.SIGTERM)

Ответ 4

Я столкнулся с подобной проблемой этой проблемой, разобрав все сигналы, о которых я заботился. Когда Ctrl + C нажата, он все равно будет передан в подпроцесс, но Python будет ждать, пока подпроцесс не выйдет перед обработкой сигнала в главном script. Это отлично работает для подпроцесса сигнала, пока подпроцесс отвечает на Ctrl + C.

class DelayedSignalHandler(object):
    def __init__(self, managed_signals):
        self.managed_signals = managed_signals
        self.managed_signals_queue = list()
        self.old_handlers = dict()

    def _handle_signal(self, caught_signal, frame):
        self.managed_signals_queue.append((caught_signal, frame))

    def __enter__(self):
        for managed_signal in self.managed_signals:
            old_handler = signal.signal(managed_signal, self._handle_signal)
            self.old_handlers[managed_signal] = old_handler

    def __exit__(self, *_):
        for managed_signal, old_handler in self.old_handlers.iteritems():
            signal.signal(managed_signal, old_handler)

        for managed_signal, frame in self.managed_signals_queue:
            self.old_handlers[managed_signal](managed_signal, frame)

Теперь мой код подпроцесса выглядит так:

    with DelayedSignalHandler((signal.SIGINT, signal.SIGTERM, signal.SIGHUP)):
        exit_value = subprocess.call(command_and_arguments)

При нажатии Ctrl + C приложению разрешается выйти до того, как сигнал будет обработан, поэтому вам не нужно беспокоиться о том, что терминал получает искаженное, потому что поток подпроцесса не был завершен одновременно с основным потоком процесса.

Ответ 5

Здесь есть парамико-обертка, ssh_decorate, которая имеет метод ctrl

from ssh_decorate import ssh_connect
ssh = ssh_connect('user','password','server')
ssh.ctrl('c')

Не может быть проще