当前位置: 首页 > 工具软件 > Pexpect > 使用案例 >

Pexpect模块使用

赵飞语
2023-12-01

1 Pexpect的作用

pexpect 是 Python 语言的类 Expect 实现,Expect 程序主要用于人机对话的模拟,就是那种系统提问,人来回答 yes/no ,或者账号登录输入用户名和密码等等的情况。

Pexpect能够产生子应用程序并控制他们,能够通过期望模式对子应用的输出做出反应。
Pexpect允许你的脚本产生子应用、控制他们像一个人类在输入命令一样,Pexcept人机模拟对话的大致过程:

1、 运行程序
2、 程序要求人的判断和输入
3、 Expect 通过关键字匹配
4、 根据关键字向程序发送符合的字符串

Pexpect的主要特点是需要Python的基本库pty,这个库只有在类Unix系统上才有

2 使用Pexpect的场景

Pexpect使用在自动交互的应用,例如SSH、SFTP、PASSWD、TELNET。
它可以被应用在使用自动设置脚本为不同的服务器自动地重复的安装软件包。也可以被应用在自动的软件测试。

3 Pexpect的使用

第1步只需要做一次,但在程序中会不停的循环第2、3步来一步一步的完成整个工作:

1、首先用 spawn 来执行一个程序
2、然后用 expect 来等待指定的关键字,这个关键字是被执行的程序打印到标准输出上面的
3、最后当发现这个关键字以后,根据关键字用 send 方法来发送字符串给这个程序

4 pexpect.spawn类何pexpect.spawnu类

原型: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解释器的操作句柄
  • command

可以是包含命令和该命令的任何参数的字符串

Pexpect不会解释任何特殊字符(如:shell原始字符 >,| 或 *),如果需要运行一个命令并将结果传送给另一个命令,要启动一个shell, EX:

child = pexpect.spawn('/bin/bash -c "ls -l | grep LOG > logs.txt"')
child.expect(pexpect.EOF)
  • timeout - 超时时间
    指定程序的默认超时时间。程序被启动之后会有输出,也会在脚本中检查输出中的关键字是否是已知并处理的,如果指定时间内没找到程序就会出错返回

4.1 expect方法

原型:expect(pattern, timeout=-1, searchwindowsize=-1, async_=False, **kw)
在流中进行搜索,直到匹配到期望的模式为止,返回匹配pattern在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()
  • timeout
    指定期望匹配的超时时间,超时则匹配结束。

为默认值(即-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
...
'''
  • async_

在Python 3.4或安装了asyncio的Python 3.3上,传递async_ = True将使此返回asyncio协程,您可以从中获得协程,以得到与该方法通常直接给出的结果相同的结果。

4.2 sendline(s=’’)方法

打包send方法,将字符串s发送给子进程,会自动增加行分隔符(windows:’\r\n’, linux:’\n’)

4.3 before/after/match/buffer - 获取程序运行输出

当 expect() 过程匹配到关键字(或者说正则表达式)之后,系统会自动给3个变量赋值,分别是 before, after 和 match

  • before

保存上一次 expect 匹配之后,除掉匹配到的关键字本身,系统缓存中剩余下来的全部内容

保存了到匹配到关键字为止,buffer缓存里面已有的所有数据。也就是说如果buffer缓存里缓存了 100 个字符的时候终于匹配到了关键字,那么 before 就是除了匹配到的关键字之前的所有字符

如果 expect() 过程中发生错误,那么 before 保存到目前位置缓存里的所有数据, after 和 match 都是 None

  • after

保存匹配到的关键字,比如你在 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匹配对象

4.3 特殊变量

  • pexpect.EOF - 匹配终止信号

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                          # 匹配到终止模式,说明已经终止
  • pexpect.TIMEOUT - 匹配超时信号

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

参考

Pexpect

 类似资料: