概述
通过本系列第一部分《探索 Pexpect,第 1 部分:剖析 Pexpect 》(请参阅参考资料)的介绍,相信大家已经对 Pexpect 的用法已经有了比较全面的了解,知道 Pexpect 是个纯 Python 语言实现的模块,使用其可以轻松方便的实现与 ssh、ftp、passwd 和 telnet 等程序的自动交互,但是读者的理解还可能只是停留在理论基础上,本文将从实际例子入手具体介绍 Pexpect 的使用场景和使用心得体验,实例中的代码读者都可以直接拿来使用,相信会对大家产生比较大的帮助。以下是本文所要介绍的所有 Pexpect 例子标题:
例 1:ftp 的使用(注:spawn、expect 和 sendline 的使用)
例 2:记录 log(注:logfile、logfile_send和logfile_read的使用)
例 3:ssh 的使用
例 4:pxssh 的使用
例 5:telnet 的使用(注:interact 的使用)
pexpect 使用 tips
  调试 pexpect 程序的 tips
  pexpect 不会解释 shell 中的元字符
  EOF 异常和 TIMEOUT 异常
  使用 run() 来替代某些的 spawn 的使用
  expect_exact() 的使用
  expect() 中正则表达式的使用 tips
  isalive() 的使用 tips
  delaybeforesend 的使用 tips

例 1:ftp 的使用
本例实现了如下功能:ftp 登录到 develperWorks.ibm.com 主机上,并用二进制传输模式下载一个名叫 rmall的文件。

#!/usr/bin/env python

import pexpect
# 即将 ftp 所要登录的远程主机的域名
ipAddress = 'develperWorks.ibm.com'
# 登录用户名
loginName = 'root'
# 用户名密码
loginPassword = 'passw0rd'

# 拼凑 ftp 命令
cmd = 'ftp ' + ipAddress
# 利用 ftp 命令作为 spawn 类构造函数的参数,生成一个 spawn 类的对象
child = pexpect.spawn(cmd)
# 期望具有提示输入用户名的字符出现
index = child.expect(["(?i)name", "(?i)Unknown host", pexpect.EOF, pexpect.TIMEOUT])
# 匹配到了 "(?i)name",表明接下来要输入用户名
if ( index == 0 ):
    # 发送登录用户名 + 换行符给子程序.
    child.sendline(loginName)
    # 期望 "(?i)password" 具有提示输入密码的字符出现.
    index = child.expect(["(?i)password", pexpect.EOF, pexpect.TIMEOUT])
    # 匹配到了 pexpect.EOF 或 pexpect.TIMEOUT,表示超时或者 EOF,程序打印提示信息并退出.
    if (index != 0):
        print "ftp login failed"
        child.close(force=True)
    # 匹配到了密码提示符,发送密码 + 换行符给子程序.
    child.sendline(loginPassword)
    # 期望登录成功后,提示符 "ftp>" 字符出现.
    index = child.expect( ['ftp>', 'Login incorrect', 'Service not available',
    pexpect.EOF, pexpect.TIMEOUT])
    # 匹配到了 'ftp>',登录成功.
    if (index == 0):
        print 'Congratulations! ftp login correct!'
        # 发送 'bin'+ 换行符给子程序,表示接下来使用二进制模式来传输文件.
        child.sendline("bin")
        print 'getting a file...'
        # 向子程序发送下载文件 rmall 的命令.
        child.sendline("get rmall")
        # 期望下载成功后,出现 'Transfer complete.*ftp>',其实下载成功后,
        # 会出现以下类似于以下的提示信息:
        #    200 PORT command successful.
        #    150 Opening data connection for rmall (548 bytes).
        #    226 Transfer complete.
        #    548 bytes received in 0.00019 seconds (2.8e+03 Kbytes/s)
        # 所以直接用正则表达式 '.*' 将 'Transfer complete' 和提示符 'ftp>' 之间的字符全省去.
        index = child.expect( ['Transfer complete.*ftp>', pexpect.EOF, pexpect.TIMEOUT] )
        # 匹配到了 pexpect.EOF 或 pexpect.TIMEOUT,表示超时或者 EOF,程序打印提示信息并退出.
        if (index != 0):
            print "failed to get the file"
            child.close(force=True)
        # 匹配到了 'Transfer complete.*ftp>',表明下载文件成功,打印成功信息,并输入 'bye',结束 ftp session.
        print 'successfully received the file'
        child.sendline("bye")
    # 用户名或密码不对,会先出现 'Login incorrect',然后仍会出现 'ftp>',但是 pexpect 是最小匹配,不是贪婪匹配,
    # 所以如果用户名或密码不对,会匹配到 'Login incorrect',而不是 'ftp>',然后程序打印提示信息并退出.
    elif (index == 1):
        print "You entered an invalid login name or password. Program quits!"
        child.close(force=True)
    # 匹配到了 'Service not available',一般表明 421 Service not available, remote server has
    # closed connection,程序打印提示信息并退出.
    # 匹配到了 pexpect.EOF 或 pexpect.TIMEOUT,表示超时或者 EOF,程序打印提示信息并退出.
    else:
        print "ftp login failed! index = " + index
        child.close(force=True)


# 匹配到了 "(?i)Unknown host",表示 server 地址不对,程序打印提示信息并退出
elif index == 1 :
    print "ftp login failed, due to unknown host"
    child.close(force=True)
# 匹配到了 pexpect.EOF 或 pexpect.TIMEOUT,表示超时或者 EOF,程序打印提示信息并退出
else:
    print "ftp login failed, due to TIMEOUT or EOF"
    child.close(force=True)

注:
运行后,输出结果为:
Congratulations! ftp login correct!
getting a file...
successfully received the file

本例 expect 函数中的 pattern 使用了 List,并包含了 pexpect.EOF和pexpect.TIMEOUT,这样出现了超时或者 EOF,不会抛出 expection 。(关于 expect() 函数的具体使用,请参阅参考资料)
如果程序运行中间出现了错误,如用户名密码错误,超时或者 EOF,远程 server 连接不上,都会使用 c hild.close(force=True) 关掉 ftp 子程序。调用 close 可以用来关闭与子程序的 connection 连接,如果你不仅想关闭与子程序的连接,还想确保子程序是真的被 terminate 终止了,设置参数 force=True,其最终会调用 c hild.kill(signal.SIGKILL) 来杀掉子程序。

例 2:记录 log
本例实现了如下功能:运行一个命令,并将该命令的运行输出结果记录到 log 文件中 ./command.py [-a] [-c command] {logfilename} -c 后接的是要运行的命令的名字,默认是“ls -l”; logfilename 是记录命令运行结果的 log 文件名,默认是“command.log”;指定 -a 表示命令的输出结果会附加在 logfilename 后,如果 logfilename 之前已经存在的话。

#!/usr/bin/env python
"""
This run a user specified command and log its result.

./command.py [-a] [-c command] {logfilename}

logfilename : This is the name of the log file. Default is command.log.
-a : Append to log file. Default is to overwrite log file.
-c : spawn command. Default is the command 'ls -l'.

Example:

This will execute the command 'pwd' and append to the log named my_session.log:

./command.py -a -c 'pwd' my_session.log

"""
import os, sys, getopt
import traceback
import pexpect

# 如果程序中间出错,打印提示信息后退出
def exit_with_usage():
    print globals()['__doc__']
    os._exit(1)

def main():
    ######################################################################
    # Parse the options, arguments, get ready, etc.
    ######################################################################
    try:
        optlist, args = getopt.getopt(sys.argv[1:], 'h?ac:', ['help','h','?'])
    # 如果指定的参数不是’ -a ’ , ‘ -h ’ , ‘ -c ’ , ‘ -? ’ , ‘ --help ’ ,
    #‘ --h ’或’ --? ’时,会抛出 exception,
    # 这里 catch 住,然后打印出 exception 的信息,并输出 usage 提示信息.
    except Exception, e:
        print str(e)
        exit_with_usage()
    options = dict(optlist)
    # 最多只能指定一个 logfile,否则出错.
    if len(args) > 1:
        exit_with_usage()
    # 如果指定的是 '-h','--h','-?','--?' 或 '--help',只输出 usage 提示信息.
    if [elem for elem in options if elem in ['-h','--h','-?','--?','--help']]:
        print "Help:"
        exit_with_usage()
    # 获取 logfile 的名字.
    if len(args) == 1:
        script_filename = args[0]
    else:
    # 如果用户没指定,默认 logfile 的名字是 command.log
        script_filename = "command.log"
    # 如果用户指定了参数 -a,如果之前该 logfile 存在,那么接下来的内容会附加在原先内容之后,
    # 如果之前没有该  logfile,新建一个文件,并且接下来将内容写入到该文件中.
    if '-a' in options:
        fout = open (script_filename, "ab")
    else:
    # 如果用户没指定参数 -a,默认按照用户指定 logfile 文件名新建一个文件,然后将接下来将内容写入到该文件中.
        fout = open (script_filename, "wb")
    # 如果用户指定了 -c 参数,那么运行用户指定的命令.
    if '-c' in options:
        command = options['-c']
    # 如果用户没有指定 -c 参数,那么默认运行命令'ls – l'
    else:
        command = "ls -l"

    # logfile 文件的 title
    fout.write ('==========Log Tile: IBM developerWorks China==========\n')

    # 为接下来的运行命令生成一个 pexpect 的 spawn 类子程序的对象.
    p = pexpect.spawn(command)
    # 将之前 open 的 file 对象指定为 spawn 类子程序对象的 log 文件.
    p.logfile = fout
    # 命令运行完后,expect EOF 出现,这时会将 spawn 类子程序对象的输出写入到 log 文件.
    p.expect(pexpect.EOF)
    #open 完文件,使用完毕后,需关闭该文件.
    fout.close()
    return 0

if __name__ == "__main__":
    try:
        main()
    except SystemExit, e:
        raise e
    except Exception, e:
        print "ERROR"
        print str(e)
        traceback.print_exc()
        os._exit(1)
View Code

相关文章: