簡體   English   中英

將Ctrl-C發送到通過subprocess.Popen和ssh啟動的遠程進程

[英]Send Ctrl-C to remote processes started via subprocess.Popen and ssh

如何將Ctrl-C發送到Popen()對象中的多個ssh -t進程?

我有一些Python代碼可以啟動遠程主機上的腳本:

# 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 ,並殺死兩個遠程進程。 一個不太理想的解決方案可能涉及確定性地找到遠程腳本的PID,但我不知道如何在我當前的設置中這樣做。

更新:在遠程服務器上啟動的腳本實際上啟動了幾個子進程,並且殺死ssh確實會殺死原始遠程腳本(可能是SIGHUP的b / c),子任務不會被終止。

我成功殺死所有子進程的唯一方法是使用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()

這足夠可靠。 我相信其他人早些時候發布了這個答案,但后來刪除了答案,所以我會發布它以防其他人好奇。

當被殺死時,ssh會向遠程進程發送一個SIGHUP。 您可以將遠程進程包裝到shell或python腳本中,當腳本收到SIGHUP時會將其終止(請參閱bash的trap命令和python中的信號模塊)

甚至可以使用膨脹的命令行而不是遠程包裝腳本來執行此操作。

問題是殺死遠程進程不是你想要的,你想要的是在你做Ctrl + C后有一個工作終端。 要做到這一點,你必須殺死遠程進程並查看剩余的輸出,其中包含一些終端控制序列以將終端重置為正確的狀態。 為此,您需要一個機制來表示包裝器腳本以終止進程。 這不是一回事。

我沒有試過這個,但也許你可以捕獲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)

我通過取消映射我關心的所有信號來解決類似的問題。 當按下Ctrl + C時,它仍將被傳遞到子進程,但是Python將等到子進程退出之后再處理主腳本中的信號。 只要子進程響應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時,允許應用程序在處理信號之前退出,因此您不必擔心終端會出現亂碼,因為子進程線程沒有與主進程線程同時終止。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM