Pexpect is a pure Python module for spawning child applications; controlling them; and responding to expected patterns in their output. Pexpect works like Don Libes’ Expect. Pexpect allows your script to spawn a child application and control it as if a human were typing commands. Pexpect can be used for automating interactive applications such as ssh, ftp, passwd, telnet, etc. It can be used to a automate setup scripts for duplicating software package installations on different servers. It can be used for automated software testing. Pexpect is in the spirit of Don Libes’ Expect, but Pexpect is pure Python. Unlike other Expect-like modules for Python, Pexpect does not require TCL or Expect nor does it require C extensions to be compiled. It should work on any platform that supports the standard Python pty module. The Pexpect interface was designed to be easy to use.

API Overview

# This connects to the openbsd ftp site and
# downloads the recursive directory listing.
import pexpect
child = pexpect.spawn('ftp ftp.openbsd.org')
child.expect('Name .*: ')
child.sendline('anonymous')
child.expect('Password:')
child.sendline('noah@example.com')
child.expect('ftp> ')
child.sendline('lcd /tmp')
child.expect('ftp> ')
child.sendline('cd pub/OpenBSD')
child.expect('ftp> ')
child.sendline('get README')
child.expect('ftp> ')
child.sendline('bye')

在pexpect中有两个重要的方法——expect()和send()(或sendline())。sendline和send类似但是它有linefeed。expect方法的等待child返回给定字符串,形参中指定的正则表达式可匹配复杂模式。send方法向child写字符串。从child角度来看就像有人从终端输入文本一样。在每次调用expect后,before和after属性会被设置为child打印的文本。before属性包含所有文本,直到期望的字符串模式而after字符串包含匹配模式的文本。match属性设置为rematch对象。

下面例子使用ftp登录到OpenBSD站点,列出目录下的文件,将ftp会话控制转让给用户。

import pexpect
child = pexpect.spawn ('ftp ftp.openbsd.org')
child.expect ('Name .*: ')
child.sendline ('anonymous')
child.expect ('Password:')
child.sendline ('noah@example.com')
child.expect ('ftp> ')
child.sendline ('ls /pub/OpenBSD/')
child.expect ('ftp> ')
print child.before # Print the result of the ls command.
child.interact() # Give control of the child to the user.

Special EOF and TIMEOUT patterns

有两个特殊模板匹配EOF和TIMEOUT,它们不是正则表达式。使用expect EOF模板,可以一直读到EOF而不会产生异常;在这种情况下,child获得的输出都在before属性中。
expect的形参可以是一个或多个正则表达式,从而允许匹配多个可选响应。expect方法返回匹配的模板的序号。比如,使用密码登录系统,系统会返回多种响应(密码被拒绝,允许进入但询问终端类型,正确进入)。

child.expect('password:')
child.sendline(my_secret_password)
# We expect any of these three patterns...
i = child.expect (['Permission denied', 'Terminal type', '[#\$] '])
if i==0:
print('Permission denied on host. Can\'t login')
child.kill(0)
elif i==1:
print('Login OK... need to send terminal type.')
child.sendline('vt100')
child.expect('[#\$] ')
elif i==2:
print('Login OK.')
print('Shell command prompt', child.after)

如果没有匹配到期望模式,expect最终会抛出TIMEOUT异常。默认时间是30秒,也可以改变等待时间。

# Wait no more than 2 minutes (120 seconds) for password prompt.
child.expect('password:', timeout=120)

Find the end of line – CR/LF conventions

Pexpect的正则表达式稍微不同,$匹配行结尾(end of line)是没有用的。$匹配字符串结尾,但Pexpect从child每次读一个字符,所以每个字符都像一行的结尾。Pexpect不能向前预读child输出流。Pexpect有内部缓冲区,读的速度比一次一个字符快,但是从用户角度来看,正则表达式每次测试一个字符。
匹配行尾的最佳方法是寻找newline("\r\n" CR/LF)。看上去像DOS风格。某些使用UNIX的人会惊讶得发现终端TTY设备驱动(dumb、、vt100、ANSI、xtrem等)都使用CR/LF组合指示行结尾。Pexpect使用Pseudo-TTY设备和child交互,当child打印"\n"时,实际看到的是"\r\n"。UNIX仅使用Linefeed来结束行,TTY设备每行结束使用CR/LF组合。当从TTY设备的UNIX命令行中取数据时,会发现TTY设备输出CR/LF组合。UNIX命令行可能输出linefeed(\n),但是TTY设备驱动将其转换为CR/LF(十六进制0D 0A)。Pexpect模拟终端,为了匹配行尾,需使用:​​child.expect('\r\n')​​​ 如果你只需跳过新行,可使用expect(’\n’)。如果在行尾匹配特定模式,需要制定\r。比如,在行尾指定匹配特定模式,需要指定\r。比如,在行尾指定匹配单词:​​child.expect('\w+\r\n')​​。下面两种写法会失败:​​child.expect('\w+\n')​​和​​child.expect ('\w+$')​​ Pexpect启用re.DOTALL标志编译所有正则表达式,在该方式下,“.”匹配新行newline。

Beware of + and * at the end of patterns

由于Pexpect不能向前预读child输出流,每次匹配总是获得最小匹配(非贪婪模式)。如下例子,​​child.expect ('.+')​​​仅返回一个字符;​​child.expect ('.*')​​返回零个字符

Debugging

如果你从pexpect.spawn对象中获得字符串,可以获得很多有用调试信息。

try:
i = child.expect ([pattern1, pattern2, pattern3, etc])
except:
print("Exception was thrown")
print("debug information:")
print(str(child))

也可以将输出定向到log文件中,下列例子会打开logging并将输出输出到stdout(sys.stdout.buffer对象仅在Python3有效,Python2使用sys.stdout)

child = pexpect.spawn(foo)
child.logfile = sys.stdout.buffer

Exceptions

有两种EOF异常可以抛出,可以不加区分,但是它们可以获得不同平台类型信息

  • “End Of File (EOF) in read(). Exception style platform.”
  • “End Of File (EOF) in read(). Empty string style platform.”
    当读处于EOF状态的文件描述符时,一些UNIX平台会抛出异常,而一些平台仅返回空字符串来指示EOF状态。如果你向读到child输出结束时不产生异常,使用expect(pexpect.EOF)方法。

expect和read都会抛出TIMEOUT异常,你可以用如下方法忽略超时或永久阻塞:​​child.expect(pexpect.EOF, timeout=None)​

Pexpect version 4.8--API Overviews_pexpect

参考:https://pexpect.readthedocs.io/en/stable/overview.html