web-dev-qa-db-ja.com

subprocess.Popenおよびsshを介して開始されたリモートプロセスにCtrl-Cを送信します

Popen()オブジェクト内の複数のCtrl-Cプロセスに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)が強制終了されますが、子タスクは強制終了されません。 。

18
aaronstacy

すべての子プロセスを正常に強制終了することができた唯一の方法は、 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()

これは十分に信頼できます。他の誰かが以前にこの回答を投稿したと思いますが、その後回答を削除したので、他の誰かが興味を持った場合に備えて投稿します。

8
aaronstacy

強制終了されると、sshはSIGHUPをリモートプロセスに送信します。リモートプロセスをシェルまたはpythonスクリプトにラップして、そのスクリプトがSIGHUPを受信したときにそれらを強制終了することができます(bashのtrapコマンド、およびpythonのシグナルモジュールを参照)。

リモートラッパースクリプトの代わりに、肥大化したコマンドラインを使用してそれを行うことさえ可能かもしれません。

問題は、リモートプロセスを強制終了することが目的ではないことです。必要なのは、Ctrl + Cを実行した後にターミナルが機能するようにすることです。これを行うには、リモートプロセスを強制終了し、残りの出力を確認する必要があります。この出力には、端末を適切な状態にリセットするための端末制御シーケンスが含まれています。そのためには、プロセスを強制終了するラッパースクリプトに信号を送るメカニズムが必要になります。これは同じことではありません。

4
BatchyX

私はこれを試していませんが、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)
2
lauret

気にしたすべての信号のマッピングを解除することで、この問題と同様の問題を回避しました。 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を押すと、シグナルが処理される前にアプリケーションを終了できるため、サブプロセススレッドがメインプロセススレッドと同時に終了しなかったために、端末が文字化けする心配がありません。

1
Eric Pruitt