Expect 程序主要用于人机对话的模拟,就是那种系统提问,人来回答 yes/no ,或者账号登录输入用户名和密码等等的情况。因为这种情况特别多而且繁琐,所以很多语言都有各种自己的实现。最初的第一个 Expect 是由 TCL 语言实现的,所以后来的 Expect 都大致参考了最初的用法和流程,整体来说大致的流程包括:
运行程序
程序要求人的判断和输入
Expect 通过关键字匹配
根据关键字向程序发送符合的字符串
pexpect 是 Python 语言的类 Expect 实现。从我的角度来看,它在功能上与 TCL 语言的实现还是有一些差距,比如没有buffer_full 事件、比如没有 expect before/after 事件等,但用来做一般的应用还是足够了
pexpect 的使用说来说去,就是围绕3个关键命令做操作:
首先用 spawn 来执行一个程序
使用 expect 来等待指定的关键字,这个关键字是被执行的程序打印到标准输出上面的
最后当发现这个关键字以后,根据关键字用 send 方法来发送字符串给这个程序
第一步只需要做一次,但在程序中会不停的循环第二、三步来一步一步的完成整个工作。掌握这个概念之后 pexpect 的使用就很容易了。当然 pexpect 不会只有这 3 个方法,实际上还有很多外围的其他方法
在本机执行命令,并输出命令执行结果
import pexpect child = pexpect.spawn('ls -l') child.expect(pexpect.EOF) result = child.before.decode() print(result)
返回
总用量 4 -rw-rw-r-- 1 sukki sukki 149 7月 14 10:42 test.py
spawn类
spawn() 方法用来执行一个程序,它返回这个程序的操作句柄,以后可以通过操作这个句柄来对这个程序进行操作
第一个参数command的使用,变量child就是 spawn() 的程序操作句柄了,之后对这个程序的所有操作都是基于这个句柄的,所以它可以说是最重要的部分
command参数也可以配合args参数使用,将上面例子可以改为
child = pexpect.spawn('ls', args = ['-l', '/']) child.expect(pexpect.EOF) result = child.before.decode() print(result)
注意:command参数不支持直接使用管道,通配符,标志输入,输出,错误重定向,如要使用就必须配合args参数
child = pexpect.spawn('/bin/bash', ['-c', 'cat test|grep green']) child.expect(pexpect.EOF) result = child.before.decode() print(result)
timeout参数:设置超时时间,单位为秒
maxread参数:从TTY读取信息最大缓冲区
logfile=None:指定日志文件,可指定为sys.stdout
cwd=None:指定命令运行的目录,默认值 None 或者说 ./
env=None:命令运行时的环境变量
encoding=None:命令运行时的编码
codec_errors=‘strict’:编码转换时的选项
child = pexpect.spawn('ls -l', logfile=sys.stdout, cwd = '/home') child.expect(pexpect.EOF)
expect()方法
当 spawn() 启动了一个程序并返回程序控制句柄后,就可以用 expect() 方法来等待指定的关键字了。它最后会返回 0 表示匹配到了所需的关键字,如果后面的匹配关键字是一个列表的话,就会返回一个数字表示匹配到了列表中第几个关键字,从 0 开始计算。
expect() 利用正则表达式来匹配所需的关键字。使用方式如下
# pattern_list 正则表达式列表,表示要匹配这些内容 # timeout 不设置或者设置为-1的话,超时时间就采用self.timeout的值,默认是30秒。也可以自己设置。 # searchwindowsize 功能和 spawn 上的一样,但是!请注意这个但是!下面会实际说明 child.expect(pattern_list, timeout=-1, searchwindowsize=None)
patter_list:可以为字符串,正则表达式,EOF,TIMEOUT,或者以上类型的列表,用以匹配子命令返回的结果。从子命令返回结果中进行匹配,若只提供字符串等非列表,匹配成功返回0;若提供列表,则返回匹配成功的列表序号;匹配失败,抛出异常
searchwindowsize:是在 expect() 方法中真正生效的,默认情况下是 None,也就是每从子进程中获取一个字符就做一次完整匹配,如果子进程的输出很多的话……性能会非常低。如果设置为其他的值,表示从子进程中读取到多少个字符才做一次匹配,这样会显著减少匹配的次数,增加性能。
child = pexpect.spawn('ls -l ./') child.expect('run') # 匹配run字符
before/after/match:获取程序运行输出
当 expect() 过程匹配到关键字(或者说正则表达式)之后,系统会自动给3个变量赋值,分别是 before, after 和 match
before - 保存了到匹配到关键字为止,缓存里面已有的所有数据。也就是说如果缓存里缓存了 100 个字符的时候终于匹配到了关键字,那么 before 就是除了匹配到的关键字之外的所有字符
after - 保存匹配到的关键字,比如你在 expect 里面使用了正则表达式,那么表达式匹配到的所有字符都在 after 里面
match - 保存的是匹配到的正则表达式的实例,和上面的 after 相比一个是匹配到的字符串,一个是匹配到的正则表达式实例
如果 expect() 过程中发生错误,那么 before 保存到目前位置缓存里的所有数据, after 和 match 都是 None
child = pexpect.spawn('ls -l ./') child.expect('run') print(child.before) print(child.match) print(child.after)
expect 方法中也可以传入一个列表,列表中的每个元素都是一个关键字的正则表达式
child = pexpect.spawn('ls -l ./') index = child.expect(['test', 'run']) # 匹配到列表中任意一个元素即停止匹配 print(index) # 返回列表中匹配到的字符索引
如果没有匹配到任何字符则抛出异常:
child = pexpect.spawn('ls -l ./') child.expect('who')
异常信息如下:
pexpect.exceptions.EOF: End Of File (EOF). Exception style platform. <pexpect.pty_spawn.spawn object at 0x7f101f7b4550> command: /bin/ls args: ['/bin/ls', '-l', './'] buffer (last 100 chars): '' before (last 100 chars): 'rw-r--r-- 1 root root 3731 Mar 20 04:21 run.py\r\n-rw-r--r-- 1 root root 178 Mar 20 22:36 test.py\r\n' after: <class 'pexpect.exceptions.EOF'> match: None match_index: None exitstatus: 0 flag_eof: True pid: 7785 child_fd: 5 closed: False timeout: 30 delimiter: <class 'pexpect.exceptions.EOF'> logfile: None logfile_read: None logfile_send: None maxread: 2000 ignorecase: False searchwindowsize: None delaybeforesend: 0.05 delayafterclose: 0.1 delayafterterminate: 0.1 searcher: searcher_re: 0: re.compile('who')
可以匹配异常,让异常不在终端显示,从而程序不退出运行:
child = pexpect.spawn('ls -l ./') child.expect(pexpect.EOF) # 如果将此行代码打印的话会输出0 child = pexpect.spawn('ls') child.expect(['run', pexpect.EOF]) # 如果返回1说明匹配到了异常
匹配时自动应用re.DOTALL正则选项。(.+ 匹配所有字符,.* 返回空字符),匹配行尾使用 ‘\r\n’,无法用$匹配行尾
sendline()方法
sendline() - 发送带回车符的字符串
sendline() 和 send() 唯一的区别就是在发送的字符串后面加上了回车换行符,这也使它们用在了不同的地方:
只需要发送字符就可以的话用send()
如果发送字符后还要回车的话,就用 sendline()
它也会返回发送的字符数量
child = pexpect.spawn('nslookup') child.expect('>') child.sendline('www.baidu.com') child.expect('>') print(child.before) child.sendline('exit')
其他发送信息的方法
send() :发送关键字
send() 作为3个关键操作之一,用来向程序发送指定的字符串,末尾不带回车换行符
process.expect("ftp>") process.send("by\n")
# 这个方法会返回发送字符的数量
sendcontrol():发送控制信号
sendcontrol() 向子程序发送控制字符,比如 ctrl+C 或者 ctrl+D 之类的,比如你要向子程序发送 ctrl+G,那么就这样写:
child.sendcontrol('g')
sendeof() :发送 EOF 信号
向子程序发送 End Of File 信号,一般用于确认上一次发送内容缓冲结束
sendintr():发送终止信号
向子程序发送 SIGINT 信号,相当于 Linux 中的 kill 2 ,它会直接终止掉子进程
write():发送字符串
类似于send()命令,只不过不会返回发送的字符数。
writelines():发送包含字符串的列表
类似于 write() 命令,只不过接受的是一个字符串列表, writelines() 会向子程序一条一条的发送列表中的元素,但是不会自动在每个元素的最后加上回车换行符。
与 write() 相似的是,这个方法也不会返回发送的字符数量。
其他获取结果的方法
expect_exact():精确匹配
它的使用和 expect() 是一样的,唯一不同的就是它的匹配列表中不再使用正则表达式。
从性能上来说 expect_exact() 要更好一些,因为即使你没有使用正则表达式而只是简单的用了几个字符 expect() 也会先将它们转换成正则表达式模式然后再搜索,但 expect_exact() 不会,而且也不会把一些特殊符号转换掉。
expect_list():预转换匹配
使用方式和 expect() 一样,唯一不同的就是匹配列表只用已编译正则表达式和EOF, TIMEOUT;提高匹配速度;expect()方法是通过它工作的。
expect() 稍微有点笨,每调用一次它都会将内部的正则表达式转换一次(当然也有其他办法避免),如果你是在以后循环中调用 expect() 的话,多余的转换动作就会降低性能,在这种情况下建议用 expect_list() 来代替。
# timeout 为 -1 的话使用 self.timeout 的值 # searchwindowsize 为 -1 的话,也使用系统默认的值 child.expect_list(pattern_list, timeout=-1, searchwindowsize=-1) expect_loop():
用于从标准输入中获取内容,loop这个词代表它会进入一个循环,必须要从标准输入中获取到关键字才会往下继续执行。
expect_loop(self, searcher, timeout=-1, searchwindowsize=-1)
read():返回剩下的所有内容
获取子程序返回的所有内容,一般情况下我们可以用 expect 来期待某些内容,然后通过 before 这样的方式来获取,但这种方式有一个前提:那就是必须先 expect 某些字符,然后才能用 before 来获取缓存中剩下的内容。
read() 的使用很不同,它期待一个 EOF 信号,然后将直到这个信号之前的所有输出全部返回,就像读一个文件那样。
一般情况下,交互式程序只有关闭的时候才会返回 EOF ,比如用 by 命令关闭 ftp 服务器,或者用 exit 命令关闭一个 ssh 连接。
这个方法使用范围比较狭窄,因为完全可以用 expect.EOF 方式来代替。当然如果是本机命令,每执行完一次之后都会返回 EOF ,这种情况下倒是很有用:
child = pexpect.spawn('ls –l') output = child.read() print(output)
可以用指定 read(size=-1) 的方式来设置返回的字符数,如果没有设置或者设置为负数则返回所有内容,正数则返回指定数量的内容,返回的内容是字符串形式。
readline():返回一行输出
返回一行输出,返回的内容包括最后的\r\n字符。
也可以设置 readline(size=-1) 来指定返回的字符数,默认是负数表示返回所有的。
readlines():返回列表模式的所有输出
返回一个列表,列表中的每个元素都是一行(包括\r\n字符)