Python之pexpect详解
⼀、引⼦
Pexpect程序主要⽤于⼈机对话的模拟,就是那种系统提问,⼈来回答yes/no,或者账号登陆输⼊⽤户名和密码等等的情况。因为这种情况特别多⽽且繁琐,所以很多语⾔都有各种⾃⼰的实现。最初的第⼀个 Expect 是由 TCL 语⾔实现的,所以后来的 Expect 都⼤致参考了最初的⽤法和流程,整体来说⼤致的流程包括:
运⾏程序
程序要求⼈的判断和输⼊
Expect 通过关键字匹配
根据关键字向程序发送符合的字符串
TCL 语⾔实现的 Expect 功能⾮常强⼤,我曾经⽤它实现了防⽕墙设备的完整测试平台。也因为它使⽤⽅便、范围⼴,⼏乎所有脚本语⾔都实现了各种各样的类似与Expect的功能,它们叫法虽然不同,但原理都相差不⼤
pexpect 是 Python 语⾔的类 Expect 实现。从我的⾓度来看,它在功能上与 TCL 语⾔的实现还是有⼀些差距,⽐如没有buffer_full 事件、⽐如没有 expect before/after 事件等,但⽤来做⼀般的应⽤还是⾜够了。
⼆、基本使⽤流程
pexpect的使⽤说来说去,就是围绕3个关键命令做操作:正则匹配到第一个关键字就停止
⾸先⽤spawn来执⾏⼀个程序
然后⽤expect来等待指定的关键字,这个关键字是被执⾏的程序打印到标准输出上⾯的
最后当发现这个关键字以后,根据关键字⽤send⽅法来发送字符串给这个程序
第⼀步只需要做⼀次,但在程序中会不停的循环第⼆、三步来⼀步⼀步的完成整个⼯作。掌握这个概念之后 pexpect 的使⽤就很容易了。当然 pexpect 不会只有这 3 个⽅法,实际上还有很多外围的其他⽅法,我们⼀个⼀个来说明
三、API
spawn()-执⾏程序
spawn()⽅法⽤来执⾏⼀个程序,它返回这个程序的操作句柄,以后可以通过操作这个句柄来对这个程序进⾏操作:
process = pexpect.spawn('df -h')
process 就是 spawn() 的程序操作句柄了,之后对这个程序的所有操作都是基于这个句柄的,所以它可以说是最重要的部分。尽量给它起个简短点的名字,不然后⾯的程序要多打不少字的。-
注意: spawn() ,或者说 pexpect 并不会转译任何特殊字符⽐如 | * 字符在Linux的shell中有特殊含义,但是在 pexpect 中不会转译它们,如果在 linux 系统中想使⽤这些符号的正确含义就必须加上 shell 来运⾏,这是很容易犯的⼀个错误。
正确的⽅式:
import pexpect
process = pexpect.spawn('df -h')
pect(pexpect.EOF)) # 打印index
timeout - 超时时间
默认值:30(单位:秒)
指定程序的默认超时时间。程序被启动之后会输出,我们也会在脚本中检查出中的关键字是否是以知并处理的,如果指定时间内没到程序就会出错返回。
maxread - 缓存设置
默认值:2000(单位:字符)
指定⼀次性试着从命令输出中读多少数据。如果设置的数字⽐较⼤,那么从 TTY 中读取数据的次数就会少⼀些。
设置为 1 表⽰关闭读缓存。
设置更⼤的数值会提⾼读取⼤量数据的性能,但会浪费更多的内存。这个值的设置与 searchwindowsize 合作会提供更多功能。
缓存的⼤⼩并不会影响获取的内容,也就是说如果⼀个命令输出超过2000个字符以后,先前缓存的字符不会丢失掉,⽽是放到其他地⽅去,当你⽤ self.before (这⾥ self 代表 spawn 的实例)还是可以取到完整的输出的。
searchwindowsize - 模式匹配阀值
默认值: None
searchwindowsize 参数是与 maxread 参数⼀起合作使⽤的,它的功能⽐较微妙,但可以显著减少缓存中有很多字符时的匹配时间。
默认情况下, expect() 匹配指定的关键字都是这样:每次缓存中取得⼀个字符时就会对整个缓存中的所有内容匹配⼀次正则表达式,你可以想像如果程序的返回特别多的时候,性能会多么的低。
设置 searchwindowsize 的值表⽰⼀次性收到多少个字符之后才匹配⼀次表达式,⽐如现在有⼀条命令会出现⼤量的输出,但匹配关键字是标准的 FTP 提⽰符 ftp> ,显然要匹配的字符只有 5 个(包括空格),但是默认情况下每当 expect 获得⼀个新字符就从头匹配⼀次这⼏个字符,如果缓存中已经有了 1W 个字符,⼀次⼀次的从⾥⾯匹配是⾮常消耗资源的,这个时候就可以设置 searchwindowsize=10, 这样 expect 就只会从最新的(最后获取的) 10 个字符中匹配关键字了,如果设置的值⽐较合适的话会显著提升性能。不⽤担⼼缓存中的字符是否会被丢弃,不管有多少输出,只要不超时就总会得到所有字符的,这个参数的设置仅仅影响匹配的⾏为。
这个参数⼀般在 expect() 命令中设置, pexpect 2.x 版本似乎有⼀个 bug ,在 spawn 中设置是不⽣效的。
logfile - 运⾏输出控制
默认值: None
当给 logfile 参数指定了⼀个⽂件句柄时,所有从标准输⼊和标准输出获得的内容都会写⼊这个⽂件中(注意这个写⼊是 copy ⽅式的),如果指定了⽂件句柄,那么每次向程序发送指令(process.send)都会刷新这个⽂件(flush)。
这⾥有⼀个很重要的技巧:如果你想看到spawn过程中的输出,那么可以将这些输出写⼊到 sys.stdout ⾥去,⽐如:
process = pexpect.spawn("ftp sw-tftp", logfile=sys.stdout)
⽤这样的⽅式可以看到整个程序执⾏期间的输⼊和输出,很适合调试。
还有⼀个例⼦:
process = pexpect.spawn("ftp sw-tftp")
logFileId = open("", 'w')
process.logfile = logFileId
注意: ⽂件⾥,既包含了程序运⾏时的输出,也包含了 spawn 向程序发送的内容,有的时候你也许不希望这样,因为某些内容出现了2次,那么还有 2 个很重要的 logfile 关联参数:
logfile_read - 获取标准输出的内容
默认值: None
记录执⾏程序中返回的所有内容,也就是去掉你发出去的命令,⽽仅仅只包括命令结果的部分:
process.logfile_read = sys.stdout
上⾯的语句会在屏幕上打印程序执⾏过程中的所有输出,但是⼀般不包含你向程序发送的命令,不过⼤部分程序都有回显机制,⽐如发命令的时候设备不光接收到命令字符串,还会反向在你的终端上把字符串显⽰出来让你明⽩哪些字符被输⼊了,这种时候也是会被这个⽅法读到的。只有那些不会回显的情况 logfile_read 才会拿不到,⽐如输⼊密码的时候。
logfile_send - 获取发送的内容
默认值: None
记录向执⾏程序发送的所有内容
process.logfile_send = sys.stdout
四、pexpect实现ssh操作
# -*- coding: utf-8 -*-
#!/usr/bin/python
import pexpect
def login_ssh_password(port,user,host,passwd):
'''函数:⽤于实现pexpect实现ssh的⾃动化⽤户密码登录'''
if port and user and host and passwd:
ssh = pexpect.spawn('ssh -p %s %s@%s' % (port,user, host))
i = pect(['password:', 'continue connecting (yes/no)?'], timeout=5)
if i == 0 :
ssh.sendline(passwd)
elif i == 1:
ssh.sendline('yes\n') # 交互认证
ssh.sendline(passwd)
index = pect (["#", pexpect.EOF, pexpect.TIMEOUT])
if index == 0:
print("logging in as root!")
ssh.interact()
elif index == 1:
print("logging process exit!")
elif index == 2:
print("logging timeout exit")
else:
print("Parameter error!")
def login_ssh_key(keyfile,user,host,port):
'''函数:⽤于实现pexpect实现ssh的⾃动化密钥登录'''
if port and user and host and keyfile:
ssh = pexpect.spawn('ssh -i %s -p %s %s@%s' % (keyfile,port,user, host))
i = pect( [pexpect.TIMEOUT,'continue connecting (yes/no)?'], timeout=2) if i == 1:
ssh.sendline('yes\n')
index = pect (["#", pexpect.EOF, pexpect.TIMEOUT])
else:
index = pect (["#", pexpect.EOF, pexpect.TIMEOUT])
if index == 0:
print("logging in as root!")
ssh.interact()
elif index == 1:
print("logging process exit!")
elif index == 2:
print("logging timeout exit")
else:
print("Parameter error!")
def main():
'''主函数:实现两种⽅式分别的登录'''
login_ssh_password('22','root','10.211.55.12','admin')
# login_ssh_key(keyfile="/tmp/id_rsa",port='22',user='root',host='192.168.1.101') if __name__ == "__main__":
main()
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论