1

Question

How do you get the output of a command with multiple lines of output using pexpect?

Example

This code works, albeit with the output smashed into one line:

child = pexpect.spawn('ping -c 3 1.1.1.1')
child.expect(pexpect.EOF)
print(child.before)

However, this code does not work:

child = pexpect.spawn('hostname')
child.expect(pexpect.EOF)
print(child.before)

child.seldline('ping -c 3 1.1.1.1')
child.expect(pexpect.EOF)
print(child.before)

How would I get this second code to work?

Background

I have commands that I need to run to get connected (replaced here with hostname) and then commands that output mulitiple lines (replaced here with ping) that I cannot seem to get the output from. If I look for any string other than EOF, I get an EOF exception...

The commands I am actually running are here if you need proof:

The answer in this other question may be deprecated because this section of code copied exactly just outputs b'' over and over again.

Alphy13
  • 160
  • 9
  • You do not have a command with multiple lines. You have multiple separate commands. You need to use two separate calls to `pexpect.spawn` here. Once you `expect` EOF, that command is done. You can't do anything more with it. – Tim Roberts Jul 27 '22 at 21:50
  • It's not commands that I can separate. It's an ssh, an lxc-attach, and another ssh. Then I run a command like ping. – Alphy13 Jul 27 '22 at 22:10
  • Are you running the `lxc-attach`, `ssh`, and `ping` INSIDE the first ssh session? If so, then you are not going to get an EOF until you end the session. You need to be waiting for strings that trigger your next action, usually the shell prompt. – Tim Roberts Jul 27 '22 at 22:14
  • I can run those commands just fine and then run a simple command like `hostname`, but if I do something multi-line (like `ping`) it doesn't work. – Alphy13 Jul 27 '22 at 22:23

1 Answers1

1

Spawn a PID that will stay open

The real question: Why the second example doesn't work.

The pexpect.spawn object ('child' here) points to a process id (PID). The example I was trying to use was not working because hostname was running and then exiting. In my real usecase, I was using ssh and then several other necessary steps before the long output command (represented by ping here).

Starting your multi-step process with a command that keeps running will solve that issue. Either of these Examples will work:

child = pexpect.spawn('ssh user@host')
child = pexpect.spawn('bin/bash')

I switched to the latter which spins up a new shell that I can interact with. This allowed me to add some error handling to the ssh connection and reuse the code several times within the one shell.

Note that if you exit the ssh connection or bash shell respectively, you will need to spawn a new 'child' to send more commands.

Use a non-blocking read

Extra Detail: Fixing the first/working example's output.

This code will return the output of the last command without changing it.

def try_read(child):
    """Based on pexpect.pxssh.try_read_prompt"""
    total_timeout = 3
    timeout = 0.5
    inter_char_timeout = 0.1
    begin = time.time()
    expired = 0
    prompt = ''
    while expired < total_timeout:
        try:
            prompt += child.read_nonblocking(size=1, timeout=timeout)
            expired = time.time() - begin # updated total time expired
            timeout = inter_char_timeout
        except TimeoutError:
            print("read ended with TimeoutError")
            break
        except pexpect.TIMEOUT:
            print("read ended with pexpect.Timeout")
            break
        except pexpect.EOF:
            print("read ended with pexpect.EOF")
            break
    return prompt
Alphy13
  • 160
  • 9