1

I am using pexpect to match the output with the expected string. I also want to print the output of the command to stdout. I currently have this:

def cliExecute(cmd):
    try:
        print("Running:", cmd)
        cli = pexpect.spawn(cmd)
        return cli
    except:
        print("Failed to execute command:", str(cmd), sys.exc_info()[0])
        print("Stopping test due to error")
        sys.exit(1)

def cliExpect(cli, expectation, timeout=30):
    try:
        # print(cli.read().decode()) Doing this will print o/p but the next command fails
        cli.expect(expectation, timeout=timeout)
    except:
        raise cliExpectFail("Failed to find: ", expectation,
            "\nLast exec line:" + str(cli.before))

The above logic matches the output when I use cliExpect but I also want to log the output I am matching to. If I add the print(cli.read().decode()) line, I will see the output but the matching fails but when I switch the expect with print, the print of output doesn't happen. Any thoughts on how to fix this?

Nikhil
  • 2,168
  • 6
  • 33
  • 51

1 Answers1

1

read and expect/expect_exact do different things:

  • read is best for forcing output, such as plots, but without a buffer size, it will read until it encounters and EOF, effectively closing the child implicitly (see pexpect.spawn.read).
  • expect reads all output after a spawn or the last send/sendline, for an open child process. expect will not usually close a child, unless it reaches a timeout (see pexpect.spawn.exact).

Here's an example of what I mean:

NOTE - Tested on Ubuntu 20.04, using Python 3.8

import pexpect

# This child executes a single command and closes implicitly
cli = pexpect.spawn("ls -l")
# I use expect_exact when using symbols like $ (also used by expect's regex)
index = cli.expect_exact(["$", pexpect.EOF, ])
if index == 0:
    print("First child still open, so use *before*:\n", cli.before)
    cli.close()
else:
    # This will happen
    print("First child closed, so use *read*:\n", cli.read())

# The child stays open after this command. You should close this child explicitly
cli = pexpect.spawn("/bin/bash")
index = cli.expect_exact(["$", pexpect.EOF, ])
if index == 0:
    # This will happen
    cli.sendline("ls -l")
    cli.expect_exact("$")
    print("Next child still open, so use *before*:\n", cli.before)
    cli.close()
else:
    print("Next child closed, so use *read*:\n", cli.read())

Output:

First child closed, so use *read*:
 b''
Next child still open, so use *before*:
 b'ls -l\r\ntotal 28\r\n-rw-rw-r-- 1 ***** ***** 3393 Dec  1 15:52 *****.py\r\n-rw-rw-r-- 1 ***** ***** 1071 Dec  1 21:54 cli.py\r\n-rw-rw-r-- 1 ***** *****  793 Nov 29 19:46 *****.py\r\n-rw-rw-r-- 1 ***** *****  796 Nov 29 19:37 *****.py\r\n-rw-rw-r-- 1 ***** ***** 1118 Nov 29 16:05 *****.py\r\n-rw-rw-r-- 1 ***** *****  709 Nov 29 16:21 *****.py\r\n-rw-rw-r-- 1 ***** *****  376 Nov 22 19:47 *****.log\r\n

As you can see, read can be hit or miss. For in-and-out CLI commands, I recommend pexpect.run instead:

import pexpect

list_of_commands = ["ls -l", ]
for c in list_of_commands:
    command_output, exitstatus = pexpect.run(c, withexitstatus=True)
    if exitstatus != 0:
        raise RuntimeError("Unable to {0}: {1}".format(c, command_output.strip()))
    print(command_output.strip().decode())

Output:

total 28
-rw-rw-r-- 1 ***** ***** 3393 Dec  1 15:52 *****.py
-rw-rw-r-- 1 ***** ***** 1071 Dec  1 21:54 cli.py
-rw-rw-r-- 1 ***** *****  793 Nov 29 19:46 *****.py
-rw-rw-r-- 1 ***** *****  796 Nov 29 19:37 *****.py
-rw-rw-r-- 1 ***** ***** 1118 Nov 29 16:05 *****.py
-rw-rw-r-- 1 ***** *****  709 Nov 29 16:21 *****.py
-rw-rw-r-- 1 ***** *****  376 Nov 22 19:47 *****.log

Good luck with your code!

Rob G
  • 673
  • 3
  • 10