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', [])
child = pexpect.spawn ('/usr/bin/ssh', ['user@example.com'])
child = pexpect.spawn ('ls', ['-latr', '/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.logfile = sys.stdout
child.expect("password:")
child.sendline("U3497DT32t")
child.expect('#')
child.sendline('ls /home')
child.expect('#')

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

# cat mylog.txt

root@192.168.1.21's password: U3497DT32t
Last login: Tue Jan  7 23:05:302014 from 192.168.1.20
[root@SN2013-08-021~]# ls /home
ls /home
cc.py            poster-0.8.1                   tarfile.tar.gz  zipfile.zip
default.tar.gz   poster-0.8.1.tar.gz            test.sh
dev              pypa-setuptools-c508be8585ab   zipfile1.zip

expect方法

expect 定义了一个子程序输出的匹配规则。方法定义:expect(pattern, timeout=-1,searchwindowsize=-1) 其中,参数 pattern 表示字符串、pexpect.EOF(指向缓冲区尾部,无匹配项)、pexpect.TIMEOUT(匹配等待超时)、正则表达式或者前面四种类型组成的列表(List),当 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_else()
elif index == 2:
    do_some_other_thing()
elif index == 3:
    do_something_completely_different()

以上代码等价于:

try:
    index = p.expect(['good', 'bad'])
    if index == 0:
        do_something()
    elif index == 1:
        do_something_else()
except EOF:
    do_some_other_thing()
except TIMEOUT:
    do_something_completely_different()

expect 方法有两个非常棒的成员:before 与 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("defore:" + child.before)
print("after:" + child.after)

运行结果如下:

defore:root@192.168.1.21's
after:password:

read相关方法

下面这些输入方法的作用都是向子程序发送响应命令,可以理解成代替了我们的标准输入键盘。

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

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)

使用 fun 函数实现如下,是不是更加简洁、精炼了?

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() 方法断开连接。

Unresolved include directive in modules/ROOT/pages/section02/ch05/ch5-02.adoc - include::example$/第五章/pexpect/simple1.py[]