pythonpexpect

Pexpect Mulitple Line Output


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.


Solution

  • 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