pexpect 是 Python 语言的类 Expect 实现,Expect 程序主要用于人机对话的模拟,就是那种系统提问,人来回答 yes/no ,或者账号登录输入用户名和密码等等的情况。
Pexpect能够产生子应用程序并控制他们,能够通过期望模式对子应用的输出做出反应。
Pexpect允许你的脚本产生子应用、控制他们像一个人类在输入命令一样,Pexcept人机模拟对话的大致过程:
1、 运行程序
2、 程序要求人的判断和输入
3、 Expect 通过关键字匹配
4、 根据关键字向程序发送符合的字符串
Pexpect的主要特点是需要Python的基本库pty,这个库只有在类Unix系统上才有
Pexpect使用在自动交互的应用,例如SSH、SFTP、PASSWD、TELNET。
它可以被应用在使用自动设置脚本为不同的服务器自动地重复的安装软件包。也可以被应用在自动的软件测试。
第1步只需要做一次,但在程序中会不停的循环第2、3步来一步一步的完成整个工作:
1、首先用 spawn 来执行一个程序
2、然后用 expect 来等待指定的关键字,这个关键字是被执行的程序打印到标准输出上面的
3、最后当发现这个关键字以后,根据关键字用 send 方法来发送字符串给这个程序
原型:class pexpect.spawn(command, args=[], timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None, ignore_sighup=False, echo=True, preexec_fn=None, encoding=None, codec_errors=‘strict’, dimensions=None, use_poll=False)
pexpect.spawn与pexpect.spawnu的区别在于前者的启动程序的运行输出为bytes类型,而后者为str字符串类型。
spawn是Pexpect的主类接口,使用此类来启动和控制子应用程序,它返回这个程序的操作句柄(pexpect.pty_spawn.spawn object),以后可以通过操作这个句柄来对这个程序进行操作
child = pexpect.spawn('python') # 其中child就是新启动的python解释器的操作句柄
可以是包含命令和该命令的任何参数的字符串
Pexpect不会解释任何特殊字符(如:shell原始字符 >,| 或 *),如果需要运行一个命令并将结果传送给另一个命令,要启动一个shell, EX:
child = pexpect.spawn('/bin/bash -c "ls -l | grep LOG > logs.txt"')
child.expect(pexpect.EOF)
原型:expect(pattern, timeout=-1, searchwindowsize=-1, async_=False, **kw)
在流中进行搜索,直到匹配到期望的模式为止,返回匹配pattern在pattern列表中索引。
指定要匹配的模式,可以是字符串、EOF、a compiled re类型,或者这些类型组成的列表,其中字符串会被编译成re type
如果pattern不是列表,则expect方法在匹配后返回0。
可将 pexcept.EOF 或 pexcept.TIMEOUT增加到pattern列表,这将导致期望匹配EOF或TIMEOUT条件,而不是引发异常
index = p.expect(['good', 'bad', pexpect.EOF, pexpect.TIMEOUT])
if index == 0:
do_something()
elif index == 1:
do_something_else()
elif index == 2:
do_some_other_thing()
elif index == 3:
do_something_completely_different()
替代如下代码:
try:
index = p.expect(['good', 'bad'])
if index == 0:
do_something()
elif index == 1:
do_something_else()
except EOF:
do_some_other_thing()
except TIMEOUT:
do_something_completely_different()
为默认值(即-1)时,expect方法会使用pexpect.spawn的timeout,超过即抛出TIMEOUT异常
如果指定了timeout为正数,则使用expect方法自己指定的timeout: 匹配操作超时,抛出TIMEOUT异常
为None则会陷入无限等待阻塞状态
In [15]: child = pexpect.spawn('python')
In [16]: child.expect('>>>> ', timeout=10)
# output
'''
...
TIMEOUT: Timeout exceeded.
<pexpect.pty_spawn.spawn object at 0x7f559ba035f8>
command: /usr/bin/python
...
'''
在Python 3.4或安装了asyncio的Python 3.3上,传递async_ = True将使此返回asyncio协程,您可以从中获得协程,以得到与该方法通常直接给出的结果相同的结果。
打包send方法,将字符串s发送给子进程,会自动增加行分隔符(windows:’\r\n’, linux:’\n’)
当 expect() 过程匹配到关键字(或者说正则表达式)之后,系统会自动给3个变量赋值,分别是 before, after 和 match
保存上一次 expect 匹配之后,除掉匹配到的关键字本身,系统缓存中剩余下来的全部内容
保存了到匹配到关键字为止,buffer缓存里面已有的所有数据。也就是说如果buffer缓存里缓存了 100 个字符的时候终于匹配到了关键字,那么 before 就是除了匹配到的关键字之前的所有字符
如果 expect() 过程中发生错误,那么 before 保存到目前位置缓存里的所有数据, after 和 match 都是 None
保存匹配到的关键字,比如你在 expect 里面使用了正则表达式,那么表达式匹配到的所有字符都在 after 里面
import pexpect
analyzer = pexpect.spawnu('python')
for newWord in ['print(1)', 'print(2)']:
index = analyzer.expect('>>> ')
print(f'before:{analyzer.before}')
print(f'after:{analyzer.after}')
analyzer.sendline(newWord )
# output
'''
before:Python 2.7.17 (default, Sep 30 2020, 13:38:04)
[GCC 7.5.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
after:>>>
before:print(1)
1
after:>>>
'''
match
保存的是匹配到的正则表达式的实例,和上面的 after 相比一个是匹配到的字符串,一个是匹配到的正则表达式实例
buffer
pexpect 中的 buffer 是一个关键,但又不能被直接操作的变量,它保存的是运行过程中每一个 expect 之后的所有内容,随时被更新。而 before/after 都是直接源于它的,而 expect 的关键字匹配本身也是在 buffer 中做匹配的。
正因为它的重要性,对这个变量中的内容需要特别的警惕。比如我们将登陆设备,发送命令,退出设备这3个步骤写进3个函数的时候,最好保证每个步骤都不会影响下一个步骤,在每个步骤开始的时候,最好做清空操作:handle.buffer = ‘’
In [97]: child = pexpect.spawn('python')
In [98]: child.expect(['>>>> ',pexpect.TIMEOUT], timeout=5)
Out[98]: 1
In [99]: child.buffer # 没有匹配成功,则输出仍在buffer中
Out[99]: b'Python 2.7.17 (default, Sep 30 2020, 13:38:04) \r\n[GCC 7.5.0] on linux2\r\nType "help", "copyright", "credits" or "license" for more information.\r\n>>> '
In [100]: child.buffer = b'' # 清空buffer
In [101]: child.sendline('print(11)')
Out[101]: 10
In [102]: child.expect(['>>> ',pexpect.TIMEOUT], timeout=5) # 匹配提示符,获取print(11)的输出结果
Out[102]: 0
In [103]: child.before
Out[103]: b'print(11)\r\n11\r\n'
In [104]: child.buffer
Out[104]: b''
expect方法匹配失败则,输出数据仍保存在buffer中, 且before是buffer数据的复制:
In [63]: child = pexpect.spawn('python')
In [64]: child.expect(['xxx',pexpect.TIMEOUT], timeout=5) # 置匹配失败
Out[64]: 1
In [65]: child.buffer # python命令的输出在buffer中,且before有同样的数据
Out[65]: b'Python 2.7.17 (default, Sep 30 2020, 13:38:04) \r\n[GCC 7.5.0] on linux2\r\nType "help", "copyright", "credits" or "license" for more information.\r\n>>> '
In [66]: child.before
Out[66]: b'Python 2.7.17 (default, Sep 30 2020, 13:38:04) \r\n[GCC 7.5.0] on linux2\r\nType "help", "copyright", "credits" or "license" for more information.\r\n>>> '
expect方法匹配成功时,会将到匹配位置之前的所有数据保存到before属性,并将匹配信息保存到after属性中,如果匹配位置之后仍有数据则继续保留在buffer中:
In [67]: child.sendline("print(11)")
Out[67]: 10
In [68]: child.expect(['xxx',pexpect.TIMEOUT], timeout=5)
Out[68]: 1
In [69]: child.buffer
Out[69]: b'Python 2.7.17 (default, Sep 30 2020, 13:38:04) \r\n[GCC 7.5.0] on linux2\r\nType "help", "copyright", "credits" or "license" for more information.\r\n>>> print(11)\r\n11\r\n>>> ' # print(11)命令的输出附加在已有数据之后
In [70]: child.before
Out[70]: b'Python 2.7.17 (default, Sep 30 2020, 13:38:04) \r\n[GCC 7.5.0] on linux2\r\nType "help", "copyright", "credits" or "license" for more information.\r\n>>> print(11)\r\n11\r\n>>> '
In [71]: child.expect(['>>> ',pexpect.TIMEOUT], timeout=5) # 正确匹配python交互界面的'>>> '提示符
Out[71]: 0
In [72]: child.buffer
Out[72]: b'print(11)\r\n11\r\n>>> ' # 一串'>>> '之前的数据被从buffer中清除,并保存到before中
In [73]: child.before # 从buffer清除的被保存到before中:匹配'>>> '之前的数据部分
Out[73]: b'Python 2.7.17 (default, Sep 30 2020, 13:38:04) \r\n[GCC 7.5.0] on linux2\r\nType "help", "copyright", "credits" or "license" for more information.\r\n'
match: 匹配到模式后,存储re.MatchObject匹配对象
EOF 变量使用范围很广泛,比如检查 ssh/ftp/telnet 连接是否终止啊,文件是否已经到达末尾啊。 pexpect 大部分脚本的最后都会检查 EOF 变量来判断是不是正常终止和退出,比如下面的代码:
In [12]: child = pexpect.spawn('python')
In [13]: child.sendline('exit()') # 退出python解释器
Out[13]: 7
In [14]: child.expect(pexpect.EOF) # 检测python解释器终止
Out[14]: 0 # 匹配到终止模式,说明已经终止
TIMEOUT 变量用来匹配超时的情况,默认情况下 expect 的超时时间是 60 秒,如果超过 60 秒还没有发现期待的关键字,就会触发这个行为
In [20]: child = pexpect.spawn('python')
In [21]: child.timeout = 10 # 将所有expet方法的超时时间都设置为10s
In [22]: child.expect(['>>>> ', pexpect.TIMEOUT])
Out[22]: 1