Python之pexpect详解

1、引子

Pexpect程序主要用于人机对话的模拟,就是那种系统提问,人来回答yes/no,或者帐号登录输入用户名和密码等等的状况。由于这种状况特别多并且繁琐,因此不少语言都有各类本身的实现。最初的第一个 Expect 是由 TCL 语言实现的,因此后来的 Expect 都大体参考了最初的用法和流程,总体来讲大体的流程包括:python

  • 运行程序
  • 程序要求人的判断和输入
  • Expect 经过关键字匹配
  • 根据关键字向程序发送符合的字符串

TCL 语言实现的 Expect 功能很是强大,我曾经用它实现了防火墙设备的完整测试平台。也由于它使用方便、范围广,几乎全部脚本语言都实现了各类各样的相似与Expect的功能,它们叫法虽然不一样,但原理都相差不大linux

pexpect 是 Python 语言的类 Expect 实现。从个人角度来看,它在功能上与 TCL 语言的实现仍是有一些差距,好比没有buffer_full 事件、好比没有 expect before/after 事件等,但用来作通常的应用仍是足够了。正则表达式

2、基本使用流程

pexpect的使用说来讲去,就是围绕3个关键命令作操做:shell

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

第一步只须要作一次,但在程序中会不停的循环第2、三步来一步一步的完成整个工做。掌握这个概念以后 pexpect 的使用就很容易了。固然 pexpect 不会只有这 3 个方法,实际上还有不少外围的其余方法,咱们一个一个来讲明windows

3、API

spawn()-执行程序

spawn()方法用来执行一个程序,它返回这个程序的操做句柄,之后能够经过操做这个句柄来对这个程序进行操做:api

process = pexpect.spawn('df -h')

process 就是 spawn() 的程序操做句柄了,以后对这个程序的全部操做都是基于这个句柄的,因此它能够说是最重要的部分。尽可能给它起个简短点的名字,否则后面的程序要多打很多字的。-缓存

注意: spawn() ,或者说 pexpect 并不会转译任何特殊字符 好比 | * 字符在Linux的shell中有特殊含义,可是在 pexpect 中不会转译它们,若是在 linux 系统中想使用这些符号的正确含义就必须加上 shell 来运行,这是很容易犯的一个错误。ssh

正确的方式:函数

import pexpect

process = pexpect.spawn('df -h')
print(process.expect(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("logfile.txt", 'w')
process.logfile = logFileId

注意: logfile.txt 文件里,既包含了程序运行时的输出,也包含了 spawn 向程序发送的内容,有的时候你也许不但愿这样,由于某些内容出现了2次,那么还有 2 个很重要的 logfile 关联参数:

logfile_read - 获取标准输出的内容

默认值: None

记录执行程序中返回的全部内容,也就是去掉你发出去的命令,而仅仅只包括命令结果的部分:

process.logfile_read = sys.stdout

上面的语句会在屏幕上打印程序执行过程当中的全部输出,可是通常不包含你向程序发送的命令,不过大部分程序都有回显机制,好比发命令的时候设备不光接收到命令字符串,还会反向在你的终端上把字符串显示出来让你明白哪些字符被输入了,这种时候也是会被这个方法读到的。只有那些不会回显的状况 logfile_read 才会拿不到,好比输入密码的时候。

logfile_send - 获取发送的内容
默认值: None

记录向执行程序发送的全部内容

process.logfile_send = sys.stdout

4、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 = ssh.expect(['password:', 'continue connecting (yes/no)?'], timeout=5)
        if i == 0 :
            ssh.sendline(passwd)
        elif i == 1:
            ssh.sendline('yes\n')  # 交互认证
            ssh.expect('password: ')
            ssh.sendline(passwd)
        index = ssh.expect (["#", 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 = ssh.expect( [pexpect.TIMEOUT,'continue connecting (yes/no)?'], timeout=2)
        if i == 1:
            ssh.sendline('yes\n')
            index = ssh.expect (["#", pexpect.EOF, pexpect.TIMEOUT])
        else:
            index = ssh.expect (["#", 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()
相关文章
相关标签/搜索