pexpect可以理解为Linux下的expect的Python封装、通过pexpect可以实现对ssh、ftp、passwd、telnet等命令行进行自动交互,而无需人工干涉来达到自动化的目的。比如我们可以模拟一个FTP登录时所有交互,包括输入主机地址、用户名、密码、上传文件等,待出现异常还可以进行尝试自动处理。

pexpect的核心组件

下面介绍pexpect的几个核心组件包括spawn类、run函数及派生类pxssh等的定义及使用方法。

spawn类

spawn是pexpect的主要类接口,功能是启动和控制子应用程序,以下是它的构造函数定义:​​class pexpect.spawn(command, args=[], timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None, ignore_sighup=True)​​。其中command参数可以是任何已知的系统命令,比如:

child = pexpect.spawn('/usr/bin/ftp') #启动ftp客户端命令
child = pexpect.spawn('/usr/bin/ssh user@example.com') #启动ssh远程连接命令
child = pexpect.spawn('ls -latr /tmp') #运行ls显示/tmp目录内容命令

当子程序需要参数时,还可以使用Python列表来代替参数项,如:

child = pexpect.spawn('/usr/bin/ftp',[]) #启动ftp客户端命令
child = pexpect.spawn('/usr/bin/ssh', ['user@example.com']) #启动ssh远程连接命令
child = pexpect.spawn('ls', ['-latr', '/tmp') #运行ls显示/tmp目录内容命令

参数timeout为等待结果的超时时间;参数maxread为pexpect从终端控制台一次读取的最大字节数,searchwindowsize参数为匹配缓冲区字符串的位置,默认是从开始位置匹配。需要注意的是,pexpect不会解析shell命令当中的元字符,包括重定向>、管道|或通配符*,当然,可以通过一个技巧来解决这个问题,将存在这三个特殊元字符的命令作为/bin/bash的参数进行调用,例如:

child = pexpect.spawn('/bin/bash -c "ls -l | grep LOG > logs.txt"')
child.expect(pexpect.EOF)

可以通过将命令的参数以Python列表的形式进行替换,从而使我们的语法变成更加清晰,下面代码等价于上面的。

shell_cmd = 'ls -l | grep LOG > logs.txt'
child = pexpect.spawn('/bin/bash', ['-c', shell_cmd])
child.expect(pexpect.EOF)

有时候调试代码时,希望获取pexpect的输入与输出信息,以便了解匹配的情况。pexpect提供了两种途径,一种为写到日志文件,另一种为输出到标准输出。写到日志文件的实现方法如下:

#写到日志
child = pexpect.spawn('some_command')
fout = file('mylog.txt','w')
child.logfile = fout
#写到标准输出
child = pexpect.spawn('some_command')
child.logfile = sys.stdout

下面为一个完整的示例,实现远程SSH登录,登录成功后显示/home目录文件清单,并通过日志文件记录所有的输入与输出。

import pexpect
import sys
child = pexpect.spawn('ssh root@192.168.1.21')
fout = file('mylog.txt','w')
child.logfile = fout
child.expect("password:")
child.sendline('U3497DT32t')
chile.expect('#')
chile.sendline('ls /home')
chile.expect('#')

以下为mylog.txt日志内容,可以看到pexpect产生的全部输入与输出信息。

root@192.168.1.21's password: U3497DT32t

Last login: Tue Jan 7 23:05:30 2014 from 192.168.1.20
[root@SN2013-08-021 ~]# ls /home
ls

expect方法定义了一个子程序输出的匹配规则,方法定义为​​expect(pattern, timeout=-1, searchwindowsize=-1)​​。其中,参数pattern表示字符串、pexpect.EOF(指向缓冲区尾部,无匹配项),当pattern为一个列表时,且不止一个表列元素被匹配,则返回的结果是子程序输出最先出现的那个元素,或者是列表最左边的元素(最小索引ID),如:

import pexpect
child = pexpect.spawn("echo 'foobar'")
print child.expect(['bar','foo','foobar'])
#输出:1,即'foo'被匹配

参数timeout指定等待匹配结果的超时时间,单位为秒。当超时被触发时,expect将匹配到pexpect.TIMEOUT;参数searchwindowsize为匹配缓冲区字符串的位置,默认是从开始位置匹配。当pexpect.EOF、pexpect.TIMEOUT作为expect的列表参数时,匹配时将返回所处列表中的索引ID,例如:

index = p.expect(['good','bad',pexpect.EOF,pexpect.TIMEOUT])
if index == 0:
do_something()
elif index == 1:
do_something()
elif index == 2:
do_something()
elif index == 3:
do_something()
#以上代码等价于
try:
index = p.expect(['good','bad'])
if index == 0:
do_something()
elif index == 1:
do_something()
except EOF:
do_something()
except TIMEOUT:
do_something()

expect方法有两个非常棒的成员befor和after。before成员保存了最近匹配成功之前的内容,after成员保存了最近匹配成功之后的内容,例如:

import pexpect
import sys
child = pexpect.spawn('ssh root@192.168.1.21')
fout = file('mylog.txt','w')
child.logfile = fout
child.expect(["password:"])
child.sendline("980405")
print "before:"+child.before
print "after:"+child.after
#运行结果:
before:root@192.168.1.21's
after:password:

read相关方法,下面这些输入方法的作用都是向子程序发送响应命令,可以理解为代替标准输入键盘

send(self,s) 发送命令,不回车
sendline(seld,s='') 发送命令,回车
sendcontrol(self,char) 发送控制字符,如child.sendcontrol('c')等价于"ctrl+c"
sendeof()

run函数

run使用pexpect进行封装的调用外部命令的函数,类似于os.system或os.popen方法,不同的是,使用run可以同时获得命令的输出结果及命令的退出状态,函数定义:​​pexpect.run(command,timeout=-1,withexitstatus=False,events=None,extra_args=None,logfile=None,cwd=None,env=None)​​。参数command可以是系统已知的任意命令,如果没有写绝对路径时将会尝试搜索命令的路径,events是一个字典,定义了expect及sendline方法的对应关系,spawn方式的例子如下:

from pexpect import *
child.spawn('scp foo user@example.com:.')
child.expect('(?i)password')
child.sendline(mypassword)

使用run实现如下:

from pexpect import *
run('scp foo user@example.com:.', events={'(?i)password':mypassword})

pxssh类

pxssh是pexpect的派生类,针对在ssh会话操作上再做一层封装,提供了基类更加直接的操作方法。pxssh类定义​​class pexpect.pxssh.pxssh(timeout=30,maxread=2000,searchwindowsize=None,logfile=None.cwd=None,env=None)​​​ pxssh常用的三个方法如下:login()建立ssh连接、logout()断开连接、prompt()等待系统提示符,用于等待命令执行结束。
下面使用pxssh类实现一个ssh连接远程主机并执行命令的示例。首先使用login方法与远程主机建立连接,再通过sendline方法发送执行的命令,prompt方法等待命令执行结束且出现系统提示符,最后使用logout断开连接。

import pxssh
import getpass
try:
s = pxssh.pxssh() #创建pxssh对象s
hostname = raw_input('hostname: ')
username = raw_input('username: ')
password = getpass.getpass('please input password: ') #接收密码输入
s.login(hostname, username, password)
s.sendline('uptime') #运行uptime命令
s.prompt() #匹配系统提示符
print s.before #打印出现系统提示符前的命令输出
s.sendline('ls -l')
s.prompt()
print s.before
s.sendline('df')
s.prompt()
print s.before
s.logout()
except pxssh.ExceptionPxssh,e:
print "pxssh failed on login."
print str(e)