0

When I'm in the Python shell (REPL?) I'm able to create a connection read from stdout of the SSH server. But when I run the same code as a script (via python3 -i script.py) it is not working.

On the server side is a text-based MUD running. After loggin in via SSH it is asking for a MUD based login.

REPL

At the end you see that 153 lines where read.

>>> import paramiko
>>> client = paramiko.SSHClient()
>>> client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
>>> client.connect(hostname='fakehost', username='fakeuser', password='fakepassword')
>>> shell = client.invoke_shell()
>>> shell.setblocking(0)
>>> shell.send('username\n')
8
>>> shell.send('password\n')
10
>>> f = shell.makefile('r')
>>> r = []
>>> while shell.recv_ready():
...     r.append(f.readline())
...
>>> print(f'Read {len(r)} lines.')
Read 153 lines.

As script

#!/usr/bin/env python3
import paramiko
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(hostname='fakehost', username='fakeuser', password='fakepassword')
shell = client.invoke_shell()
shell.setblocking(0)
shell.send('username\n')
shell.send('password\n')

f = shell.makefile('r')
r = []
while shell.recv_ready():
    r.append(f.readline())

print(f'Read {len(r)} lines.')

The output here is just Read 1 lines.. Where are the other 152 lines are gone?

buhtz
  • 10,774
  • 18
  • 76
  • 149

1 Answers1

1

If there's nothing to read when the function is first called, the loop exits immediately. You need to wait firset for it to become ready, then you can keep looping until it's no longer ready.

def get_output(shell: paramiko.channel.Channel) -> str:
    """Read from shell's stdout if data is available and return it as one string"""
    result = []

    while not shell.recv_ready():
        time.sleep(1)

    while shell.recv_ready():
        with shell.makefile('r') as stdout:
            time.sleep(.5)
            line = stdout.readline()

            line = ''.join(filter(
                lambda char: not char in ['\n', '\r'],
                line))

            line = line.strip()

            if line:
                result.append(line)

    return '\n'.join(result)
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • Nice approach. But sorry this doesn't work ether. Somethings is lost on the way. I'm not able to receive all expected lines. Something must happen in the back. – buhtz Feb 28 '23 at 18:56
  • 1
    Using `shell.recv_ready()` as a condition is unreliable if the output is bursty with pauses between bursts, since you'll stop whenever there's a pause. It would probably be better to use `pexpect` to wait for a prompt. – Barmar Feb 28 '23 at 19:42
  • Sorry I totally modified my initial question and improved the MWE. The problem is not about the function but between running as scrpit or running in the REPL. btw: Couldn't find `pexpect` in the paramiko docu. – buhtz Feb 28 '23 at 21:31
  • 1
    It's not part of paramiko. See https://pexpect.readthedocs.io/en/stable/ – Barmar Feb 28 '23 at 22:07