首页 - 信息 - python pexpect的使用介绍

python pexpect的使用介绍

2023-10-06 13:02

pexpect是expect语言的Python实现。它是一个Python模块,用于启动子程序并使用正则表达式对程序输出做出特定响应,以实现与其自动交互。

pexpect用途广泛,可以用来实现与ssh、ftp、telnet等程序的自动交互。

基本使用流程

pexpect 的使用基于三个键盘命令:

  • 首先使用spawn执行程序
  • 然后使用expect等待指定的关键字。这个关键字被执行的程序打印到标准输出
  • 最后当找到这个关键字时,根据key使用send方法将字符串发送到这个程序

第一步只需要做一次,但第二步和第三步会在程序中不断循环,一步步完成整个工作。

掌握了这个概念后,使用pexpect就很容易了。当然pexpect不仅仅只有这三个方法。其实周边方法还有很多,

spawn() - 执行程序

spawn() 方法用于执行程序。它返回程序的操作句柄。以后可以通过操作这个手柄来操作程序,如:

进程 = pexpect.spawn('ftp sw-tftp') 

上面spawn()中的字符串就是要执行的程序。这里我们打开一个到 sw-tftp 服务器的 ftp 连接。
spawn() 中的第一个元素是要执行的命令。另外,还可以指定一些其他参数,如: pexpect.spawn('ftp sw-tftp', timeout=60) 指定超时时间。

process是spawn()的程序操作句柄。后续对这个程序的所有操作都是基于这个句柄,所以可以说是最重要的部分。

注意:spawn()或pexpect不会转义任何特殊字符,例如|和 * 字符在 Linux shell 中具有特殊含义,但在 pexpect 中不会被转义。如果在Linux系统中想要使用这些符号的正确含义,就必须添加一个shell来运行它。这是一个很容易犯的错误。
正确做法:

process = pexpect.spawn('/bin/bash –c "ls –l | grep LOG > log_list.txt"'  )
过程.预期(pexpect.EOF )

spawn() 还有一种调用方式:

cmd = "ls –l | grep LOG > log_list.txt"
进程 = pexpect.生成("/bin/bash",["-c",cmd]
过程.预期(pexpect.EOF )

spawn 的可选参数包括以下(仅列出部分参数):

timeout - 超时时间

默认值:30(单位:秒)

指定程序的默认超时。程序启动后会有输出。我们还将在脚本中检查输出中的关键字是否已知并已处理。如果在指定时间内没有找到该程序,将返回错误。

maxread - 缓存设置

默认值:2000(单位:字符)

指定一次尝试从命令输出读取多少数据。如果设置的数字较大,则从 TTY 读取的次数将会较少。

设置为 1 以关闭读取缓存。

searchwindowsize - 模式匹配阈值

默认值:无

searchwindowsize 参数与 maxread 参数结合使用。它的作用很微妙,但是当缓存中的字符较多时,可以显着减少匹配时间。

默认情况下,expect()是这样匹配指定关键字的:每次从缓存中获取到一个字符,就会将整个缓存中的所有内容都与一个正则表达式进行匹配。你可以想象一下,如果程序的返回特别多的话,性能会很低。

设置searchwindowsize的值以指示在匹配表达式之前一次接收多少个字符。例如,有一个命令会产生大量的输出,但匹配的关键字是标准的FTP提示符ftp>,显然需要匹配。只有5个字符(包括空格),但默认情况下,每当expect获得一个新字符时,它都会从头开始匹配这些字符。如果缓存中已经有1W个字符,一次又一次地匹配它们是非常消耗资源的。 ,此时可以设置searchwindowsize=10,这样expect只会匹配最新(最后获取的)10个字符中的关键字。如果设置的值比较合适,性能会有明显的提升。不用担心缓存中的字符是否会被丢弃。不管有多少输出,只要不超时,你总是会得到所有的字符。该参数的设置仅影响匹配行为。

这个参数一般在expect()命令中设置。 pexpect 2.x版本好像有bug,spawn中的设置不生效。

日志文件 - 运行输出控制

默认值:无

当logfile参数指定文件句柄时,所有从标准输入和标准输出获取的内容都会写入到这个文件中(注意,这种写入是复制模式)。如果指定了文件句柄,那么每次向程序发送指令(process.send)都会刷新这个文件(flush)。

这里有一个非常重要的提示:如果你想在spawn过程中看到输出,你可以将输出写入sys.stdout,例如:

进程 = pexpect.生成("ftp sw-tftp", 日志文件=sys.stdout)

这样就可以看到整个程序执行过程中的输入和输出,非常适合调试。

另一个例子:

进程 = pexpect.生成("ftp sw-tftp") 
logFileId = open("logfile.txt", 'w')
进程.logfile = logFileId

注:logfile.txt文件既包含程序运行时的输出,也包含spawn发送给程序的内容。有时你可能不希望这样,因为有些内容出现了两次,所以它仍然有2个非常重要的日志文件相关参数:

logfile_read - 获取标准输出的内容

默认值:无

记录程序执行过程中返回的所有内容,即去掉你发出的命令,只包含命令的结果:

进程.logfile_read = sys.stdout

上面的语句会将程序执行过程中的所有输出打印到屏幕上,但一般不包括你发送给程序的命令。然而,大多数程序都有回显机制。例如,发送命令时,设备不仅接收到命令字符串,还会反向显示在您的终端上,让您了解输入了哪些字符。在这种情况下,也会通过这个方法来读取。 logfile_read只有在没有echo的情况下才会获取不到,比如输入密码的时候。

logfile_send - 获取发送的内容

默认值:无

记录发送给执行者的所有内容:

进程.logfile_send = sys.stdout

上面的语句只是将发送到程序的内容打印在屏幕上。

cwd - 指定执行命令的目录

默认值:无或./

cwd 用于指定命令发送的命令执行的路径。一般用在send()系列命令中。例如,在Linux中,如果你想在/etc目录下执行ls –l命令,那么就完全没有必要。您需要使用 sendline("cd /etc && ls -l")。相反,请使用 sendline("ls –l", cwd="/etc")

env - 指定环境变量

默认值:无

指定环境变量的值。这个值是一个字典。如果您发送的命令使用了一些环境变量,您可以在这里提供它

expect() - 关键字匹配

spawn()启动一个程序并返回程序控制句柄后,可以使用expect()方法等待指定的关键字。最后会返回0,表示匹配到所需的关键字。如果后续匹配的关键字是列表,则返回一个数字,表示匹配到列表中的哪个关键字,从0开始。

expect() 使用正则表达式来匹配所需的关键字。

#pattern_list正则表达式列表,表示要匹配这些内容
#如果timeout没有设置或者设置为-1,超时会使用self.timeout的值,默认是30秒。您也可以自己设置。 
# searchwindowsize 功能与spawn相同
进程预期(pattern_list,超时= -1, searchwindowsize=无)

这里的searchwindowsize在expect()方法中确实有效。默认为None,即每次从子进程获取一个字符就进行一次完全匹配。如果子进程输出很多...性能会很低。如果设置为其他值,则表示在执行匹配之前从子进程读取了多少个字符。这将显着减少匹配次数并提高性能。

最简单的搭配方式
过程.预期('[Nn]ame')

上面代码的意思是:匹配进程句柄中的name关键字(代表我们在spawn方法的例子中启动的ftp连接),其中n不区分大小写。

上述关键字一旦匹配,就会返回0表示匹配成功,但是如果没有匹配到怎么办?默认是永远等待,但是如果设置了超时,就会超时。

匹配一系列输出

事实上,expect()可以匹配一系列输出,通过检查匹配的输出,我们可以做不同的事情。比如前面的spawn ftp连接中,如果我们输入用户名后出现不同的情况,我们可以通过监控这些不同的情况采取不同的动作,如:

索引 = 进程.预期(['权限被拒绝', '端子类型', 'ftp>',
])
if索引== 0:打印“主机权限被拒绝,无法登录。”进程kill0)elif index == 1:print "登录成功,设置终端类型…"进程.sendline('vty100')过程.预期("ftp>")
elif index == 2:print "登录成功,请发送您的命令"进程.交互()

上面的代码中,expect方法是一个列表,列表中的每个元素都是一个关键字的正则表达式。也就是说,我们期望这三种情况之一,并且期望返回一个顺序值。为了表示我匹配的是哪个元素(即发生了什么),这个序列值是从0开始计算的。

小贴士

如果要检查或匹配expect.EOF和expect.TIMEOUT,必须将它们放入匹配列表中,以便可以通过检查返回的数字来处理它们。如果没有放入列表中,就会出现EOF或TIMEOUT错误,程序会中途停止。

expect_exact() - 完全匹配

它的用法与expect()相同,唯一的区别是它的匹配列表中不再使用正则表达式。

Expect_exact() 在性能方面更好,因为即使你不使用正则表达式而只是简单地使用几个字符,expect() 也会先将它们转换为正则表达式模式然后再搜索,但expect_exact() 不会,并且不会转换一些特殊符号。

send() - 发送关键字

send()是三个关键操作之一,用于向程序发送指定的字符串。它的用途并没有什么特别之处,比如:

处理预期("ftp>")处理发送("by\n")

此方法返回发送的字符数。

sendline() - 发送带有回车符的字符串

sendline() 和 send() 唯一的区别是在发送的字符串后面添加了回车和换行符,这也使得它们使用的地方不同:

  • 如果只需要发送字符,使用send()
  • 如果需要发送字符后回车,使用sendline()

还会返回发送的字符数

sendcontrol() - 发送控制信号

sendcontrol()向子程序发送控制字符,如ctrl+Cctrl+D之类,例如你要发送 至子程序ctrl+G ,然后这样写:

处理发送控制('g')

简单的例子

命令 = 'ssh '+用户名+'@'+ 主持人
child = pexpect.spawn命令
ret = 孩子.期望([pexpect.超时,'您确定要继续连接吗','[P|p]密码']+提示
if ret == 0: 打印('[-]连接错误') 重新打开if ret == 1: 子.sendline('是') ret = 孩子.期望([pexpect.超时,'[p|P]密码' ])如果 ret == 0: 打印('[-]连接错误') 返回 if ret == 1:send_command (密码)返回
if ret == 2: send_command(密码)返回
归来孩子

本文主要转载自以下博文。更多信息请参考这篇博文:
Python Pexpect 模块使用说明