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つの問題があります。
shutdown()
コマンドを使用することを考えましたが、それは面倒なようです。チャネルの標準入力に_Ctrl-C
_を送信するようなことは可能ですか?readline()
ブロック、そして出力を取得する非ブロッキングメソッドがあればスレッドを回避できます-何か考えはありますか?1)必要に応じて、クライアントを閉じることができます。反対側のサーバーは、テールプロセスを強制終了します。
2)これを非ブロッキング方式で行う必要がある場合は、チャネルオブジェクトを直接使用する必要があります。次に、channel.recv_ready()とchannel.recv_stderr_ready()を使用して、stdoutとstderrの両方を監視するか、select.selectを使用できます。
クライアントで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
_秒に設定しました。必要に応じて、ゼロ以外のタイムアウトでブロックすることもできます。
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)
参考までに、channel.get_pty()を使用してこれを行うソリューションがあります。詳細については、以下をご覧ください: https://stackoverflow.com/a/11190727/1480181
プロセスを閉じるには、次のコマンドを実行します。
interface.close()
ノンブロッキングに関しては、ノンブロッキングの読み取りを取得することはできません。最善の方法は、一度に1つの「ブロック」を解析することです。「stdout.read(1)」は、バッファーに文字が残っていない場合にのみブロックします。
私がこれを解決した方法は、コンテキストマネージャーを使用することです。これにより、長時間実行されているコマンドが確実に中止されます。重要なロジックは、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