Long-running ssh commands in python paramiko module (and how to end them)

后端 未结 6 1753
野性不改
野性不改 2020-11-30 02:39

I want to run a tail -f logfile command on a remote machine using python\'s paramiko module. I\'ve been attempting it so far in the following fashion:

相关标签:
6条回答
  • 2020-11-30 03:01

    1) You can just close the client if you wish. The server on the other end will kill the tail process.

    2) If you need to do this in a non-blocking way, you will have to use the channel object directly. You can then watch for both stdout and stderr with channel.recv_ready() and channel.recv_stderr_ready(), or use select.select.

    0 讨论(0)
  • 2020-11-30 03:03

    To close the process simply run:

    interface.close()
    

    In terms of nonblocking, you can't get a non-blocking read. The best you would be able to to would be to parse over it one "block" at a time, "stdout.read(1)" will only block when there are no characters left in the buffer.

    0 讨论(0)
  • 2020-11-30 03:06

    Just for information, there is a solution to do this using channel.get_pty(). Fore more details have a look at: https://stackoverflow.com/a/11190727/1480181

    0 讨论(0)
  • 2020-11-30 03:09

    The way I've solved this is with a context manager. This will make sure my long running commands are aborted. The key logic is to wrap to mimic SSHClient.exec_command but capture the created channel and use a Timer that will close that channel if the command runs for too long.

    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
    

    To use the code it's pretty simple now, the first example will throw a 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
    

    This code will work fine (unless the host is under insane load!)

    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 讨论(0)
  • 2020-11-30 03:21

    Just a small update to the solution by Andrew Aylett. The following code actually breaks the loop and quits when the external process finishes:

    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)
    
    0 讨论(0)
  • 2020-11-30 03:22

    Instead of calling exec_command on the client, get hold of the transport and generate your own channel. The channel can be used to execute a command, and you can use it in a select statement to find out when data can be read:

    #!/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)
    

    The channel object can be read from and written to, connecting with stdout and stdin of the remote command. You can get at stderr by calling channel.makefile_stderr(...).

    I've set the timeout to 0.0 seconds because a non-blocking solution was requested. Depending on your needs, you might want to block with a non-zero timeout.

    0 讨论(0)
提交回复
热议问题