web-dev-qa-db-ja.com

python paramikoモジュールの長期実行sshコマンド(およびそれらを終了する方法))

Pythonのparamikoモジュールを使用して、リモートマシンで_tail -f logfile_コマンドを実行したいと思います。私はこれまで次の方法でそれを試みてきました:

_interface = paramiko.SSHClient()
#snip the connection setup portion
stdin, stdout, stderr = interface.exec_command("tail -f logfile")
#snip into threaded loop
print stdout.readline()
_

必要なだけコマンドを実行したいのですが、2つの問題があります。

  1. これをきれいに止めるにはどうすればよいですか?私はチャネルを作成し、それが終わったらチャネルでshutdown()コマンドを使用することを考えましたが、それは面倒なようです。チャネルの標準入力に_Ctrl-C_を送信するようなことは可能ですか?
  2. readline()ブロック、そして出力を取得する非ブロッキングメソッドがあればスレッドを回避できます-何か考えはありますか?
22
user17925

1)必要に応じて、クライアントを閉じることができます。反対側のサーバーは、テールプロセスを強制終了します。

2)これを非ブロッキング方式で行う必要がある場合は、チャネルオブジェクトを直接使用する必要があります。次に、channel.recv_ready()とchannel.recv_stderr_ready()を使用して、stdoutとstderrの両方を監視するか、select.selectを使用できます。

14
JimB

クライアントでexec_commandを呼び出す代わりに、トランスポートを取得して独自のチャネルを生成します。 channel を使用してコマンドを実行できます。また、selectステートメントでそれを使用して、いつデータを読み取ることができるかを確認できます。

_#!/usr/bin/env python
import paramiko
import select
client = paramiko.SSHClient()
client.load_system_Host_keys()
client.connect('Host.example.com')
transport = client.get_transport()
channel = transport.open_session()
channel.exec_command("tail -f /var/log/everything/current")
while True:
  rl, wl, xl = select.select([channel],[],[],0.0)
  if len(rl) > 0:
      # Must be stdout
      print channel.recv(1024)
_

チャネルオブジェクトは、リモートコマンドのstdoutおよびstdinに接続して、読み書きできます。 channel.makefile_stderr(...)を呼び出すと、stderrにアクセスできます。

非ブロッキングソリューションが要求されたため、タイムアウトを_0.0_秒に設定しました。必要に応じて、ゼロ以外のタイムアウトでブロックすることもできます。

21
Andrew Aylett

AndrewAylettによるソリューションのほんの少しの更新。次のコードは、実際にはループを中断し、外部プロセスが終了すると終了します。

import paramiko
import select

client = paramiko.SSHClient()
client.load_system_Host_keys()
client.connect('Host.example.com')
channel = client.get_transport().open_session()
channel.exec_command("tail -f /var/log/everything/current")
while True:
    if channel.exit_status_ready():
        break
    rl, wl, xl = select.select([channel], [], [], 0.0)
    if len(rl) > 0:
        print channel.recv(1024)
8

参考までに、channel.get_pty()を使用してこれを行うソリューションがあります。詳細については、以下をご覧ください: https://stackoverflow.com/a/11190727/1480181

0
Sven

プロセスを閉じるには、次のコマンドを実行します。

interface.close()

ノンブロッキングに関しては、ノンブロッキングの読み取りを取得することはできません。最善の方法は、一度に1つの「ブロック」を解析することです。「stdout.read(1)」は、バッファーに文字が残っていない場合にのみブロックします。

0
lfaraone

私がこれを解決した方法は、コンテキストマネージャーを使用することです。これにより、長時間実行されているコマンドが確実に中止されます。重要なロジックは、SSHClient.exec_commandを模倣するようにラップすることですが、作成されたチャネルをキャプチャし、コマンドの実行時間が長すぎる場合にそのチャネルを閉じるTimerを使用します。

import paramiko
import threading


class TimeoutChannel:

    def __init__(self, client: paramiko.SSHClient, timeout):
        self.expired = False
        self._channel: paramiko.channel = None
        self.client = client
        self.timeout = timeout

    def __enter__(self):
        self.timer = threading.Timer(self.timeout, self.kill_client)
        self.timer.start()

        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Exited Timeout. Timed out:", self.expired)
        self.timer.cancel()

        if exc_val:
            return False  # Make sure the exceptions are re-raised

        if self.expired:
            raise TimeoutError("Command timed out")

    def kill_client(self):
        self.expired = True
        print("Should kill client")
        if self._channel:
            print("We have a channel")
            self._channel.close()

    def exec(self, command, bufsize=-1, timeout=None, get_pty=False, environment=None):
        self._channel = self.client.get_transport().open_session(timeout=timeout)
        if get_pty:
            self._channel.get_pty()
        self._channel.settimeout(timeout)
        if environment:
            self._channel.update_environment(environment)
        self._channel.exec_command(command)
        stdin = self._channel.makefile_stdin("wb", bufsize)
        stdout = self._channel.makefile("r", bufsize)
        stderr = self._channel.makefile_stderr("r", bufsize)
        return stdin, stdout, stderr

コードを使用するために非常に簡単になりました。最初の例ではTimeoutError

ssh = paramiko.SSHClient()
ssh.connect('hostname', username='user', password='pass')

with TimeoutChannel(ssh, 3) as c:
    ssh_stdin, ssh_stdout, ssh_stderr = c.exec("cat")    # non-blocking
    exit_status = ssh_stdout.channel.recv_exit_status()  # block til done, will never complete because cat wants input

このコードは正常に機能します(ホストに異常な負荷がかかっている場合を除く)。

ssh = paramiko.SSHClient()
ssh.connect('hostname', username='user', password='pass')

with TimeoutChannel(ssh, 3) as c:
    ssh_stdin, ssh_stdout, ssh_stderr = c.exec("uptime")    # non-blocking
    exit_status = ssh_stdout.channel.recv_exit_status()     # block til done, will complete quickly
    print(ssh_stdout.read().decode("utf8"))                 # Show results
0
AndrewWhalan