python pexpect模块的使用

Pexpect 是一个用来启动子程序并对其进行自动控制的 Python 模块。 Pexpect 可以用来和像 ssh、ftp、passwd、telnet 等命令行程序进行自动交互。

pexpect模块在windows功能有限,在linux系统中用得比较多。

pexpect有两个类或方法用得比较多,一个是pexpect.run() 和pexpect.spawn()

pexpect.run()类似于os.system(),但pexpect.run()会返回执行结果

pexpect.spawn()这个类的功能比较强大,可以用于以下功能:

  • 1. ftp 的使用(注:spawn、expect 和 sendline 的使用)
  • 2. 记录 log(注:logfile、logfile_sendlogfile_read的使用)
  • 3. ssh 的使用
  • 4. pxssh 的使用
  • 5. telnet 的使用(注:interact 的使用)

使用注意事项:

1.不能使用linux命令中的管道符重定向符等

2.spawn里面的参数列表里面不要使用变量名

以下是实例:

1.pexpect连接ftp:

#!/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)

2.记录log的日志代码
#!/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==========
')

    # 为接下来的运行命令生成一个 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)

注:

  • 运行:./command.py -a -c who cmd.log
运行结束后,cmd.log 的内容为:
IBM developerWorks China
Root 	 :0 		 2009-05-12 22:40
Root 	 pts/1 		 2009-05-12 22:40 (:0.0)
Root 	 pts/2 		 2009-07-05 18:55 (9.77.180.94)
  • logfile

只能通过 spawn 类的构造函数指定。在 spawn 类的构造函数通过参数指定 logfile 时,表示开启或关闭 logging 。所有的子程序的 input 和 output 都会被 copy 到指定的 logfile 中。设置 logfile 为 None 表示停止 logging,默认就是停止 logging 。设置 logfile 为 sys.stdout,会将所有东西 echo 到标准输出。

  • logfile_readlogfile_send:

logfile_read:只用来记录 python 主程序接收到 child 子程序的输出,有的时候你不想看到写给 child 的所有东西,只希望看到 child 发回来的东西。 logfile_send:只用来记录 python 主程序发送给 child 子程序的输入 logfile、logfile_read 和 logfile_send 何时被写入呢? logfile、logfile_read 和 logfile_send 会在每次写 write 和 send 操作后被 flush 。

    • 调用 send 后,才会往 logfile 和 logfile_send 中写入,sendline/sendcontrol/sendoff/write/writeline 最终都会调用 send,所以 sendline 后 logfile 中一定有内容了,只要此时 logfile 没有被 close 。
    • 调用 read_nonblocking 后,才会往 logfile 和 logfile_read 中写入,expect_loop 会调用 read_nonblocking,而 expect_exact 和 expect_list 都会调用 expect_loop,expect 会调用 expect_list,所以 expect 后 logfile 中一定有内容了,只要此时 logfile 没有被 close 。
  • 如果调用的函数最终都没有调用 send 或 read_nonblocking,那么 logfile 虽然被分配指定了一个 file,但其最终结果是:内容为空。见下例:

清单 3. log 内容为空的例子代码

import pexpect
p = pexpect.spawn( ‘ ls -l ’ )
fout = open ('log.txt', "w")
p.logfile = fout
fout.close()

运行该脚本后,你会发现其实 log.txt 是空的,没有记录 ls -l 命令的内容,原因是没有调用 send 或 read_nonblocking,真正的内容没有被 flush 到 log 中。如果在 fout.close() 之前加上 p.expect(pexpect.EOF),log.txt 才会有 ls -l 命令的内容。

3.ssh的使用

#!/usr/bin/env python

"""
This runs a command on a remote host using SSH. At the prompts enter hostname,
user, password and the command.
"""

import pexpect
import getpass, os

#user: ssh 主机的用户名
#host:ssh 主机的域名
#password:ssh 主机的密码
#command:即将在远端 ssh 主机上运行的命令
def ssh_command (user, host, password, command):
    """
    This runs a command on the remote host. This could also be done with the
    pxssh class, but this demonstrates what that class does at a simpler level.
    This returns a pexpect.spawn object. This handles the case when you try to
    connect to a new host and ssh asks you if you want to accept the public key
    fingerprint and continue connecting.
    """
    ssh_newkey = 'Are you sure you want to continue connecting'
    # 为 ssh 命令生成一个 spawn 类的子程序对象.
    child = pexpect.spawn('ssh -l %s %s %s'%(user, host, command))
    i = child.expect([pexpect.TIMEOUT, ssh_newkey, 'password: '])
    # 如果登录超时,打印出错信息,并退出.
    if i == 0: # Timeout
        print 'ERROR!'
        print 'SSH could not login. Here is what SSH said:'
        print child.before, child.after
        return None
    # 如果 ssh 没有 public key,接受它.
    if i == 1: # SSH does not have the public key. Just accept it.
        child.sendline ('yes')
        child.expect ('password: ')
        i = child.expect([pexpect.TIMEOUT, 'password: '])
        if i == 0: # Timeout
        print 'ERROR!'
        print 'SSH could not login. Here is what SSH said:'
        print child.before, child.after
        return None
    # 输入密码.
    child.sendline(password)
    return child

def main ():
    # 获得用户指定 ssh 主机域名.
    host = raw_input('Hostname: ')
    # 获得用户指定 ssh 主机用户名.
    user = raw_input('User: ')
    # 获得用户指定 ssh 主机密码.
    password = getpass.getpass()
    # 获得用户指定 ssh 主机上即将运行的命令.
    command = raw_input('Enter the command: ')
    child = ssh_command (user, host, password, command)
    # 匹配 pexpect.EOF
    child.expect(pexpect.EOF)
    # 输出命令结果.
    print child.before

if __name__ == '__main__':
    try:
        main()
    except Exception, e:
        print str(e)
        traceback.print_exc()
        os._exit(1)


4. pxssh的使用
#!/usr/bin/env python
import pxssh
import getpass
try:
    # 调用构造函数,创建一个 pxssh 类的对象.
    s = pxssh.pxssh()
    # 获得用户指定 ssh 主机域名.
    hostname = raw_input('hostname: ')
    # 获得用户指定 ssh 主机用户名.
    username = raw_input('username: ')
    # 获得用户指定 ssh 主机密码.
    password = getpass.getpass('password: ')
    # 利用 pxssh 类的 login 方法进行 ssh 登录,原始 prompt 为'$' , '#'或'>'
    s.login (hostname, username, password, original_prompt='[$#>]')
    # 发送命令 'uptime'
    s.sendline ('uptime')
    # 匹配 prompt
    s.prompt()
    # 将 prompt 前所有内容打印出,即命令 'uptime' 的执行结果.
    print s.before
    # 发送命令 ' ls -l '
    s.sendline ('ls -l')
    # 匹配 prompt
    s.prompt()
    # 将 prompt 前所有内容打印出,即命令 ' ls -l ' 的执行结果.
    print s.before
    # 退出 ssh session
    s.logout()
except pxssh.ExceptionPxssh, e:
    print "pxssh failed on login."
    print str(e)

5.telnet 的使用

#!/usr/bin/env python
import pexpect

# 即将 telnet 所要登录的远程主机的域名
ipAddress = 'develperWorks.ibm.com'
# 登录用户名
loginName = 'root'
# 用户名密码
loginPassword = 'passw0rd'
# 提示符,可能是’ $ ’ , ‘ # ’或’ > ’
loginprompt = '[$#>]'

# 拼凑 telnet 命令
cmd = 'telnet ' + ipAddress
# 为 telnet 生成 spawn 类子程序
child = pexpect.spawn(cmd)
# 期待'login'字符串出现,从而接下来可以输入用户名
index = child.expect(["login", "(?i)Unknown host", pexpect.EOF, pexpect.TIMEOUT])
if ( index == 0 ):
    # 匹配'login'字符串成功,输入用户名.
    child.sendline(loginName)
    # 期待 "[pP]assword" 出现.
    index = child.expect(["[pP]assword", pexpect.EOF, pexpect.TIMEOUT])
    # 匹配 "[pP]assword" 字符串成功,输入密码.
    child.sendline(loginPassword)
    # 期待提示符出现.
    child.expect(loginprompt)
    if (index == 0):
        # 匹配提示符成功,输入执行命令 'ls -l'
        child.sendline('ls -l')
        # 立马匹配 'ls -l',目的是为了清除刚刚被 echo 回显的命令.
        child.expect('ls -l')
        # 期待提示符出现.
        child.expect(loginprompt)
        # 将 'ls -l' 的命令结果输出.
        print child.before
        print "Script recording started. Type ^] (ASCII 29) to escape from the script 
              shell."
        # 将 telnet 子程序的执行权交给用户.
        child.interact()
        print 'Left interactve mode.'
    else:
        # 匹配到了 pexpect.EOF 或 pexpect.TIMEOUT,表示超时或者 EOF,程序打印提示信息并退出.
        print "telnet login failed, due to TIMEOUT or EOF"
        child.close(force=True)
else:
    # 匹配到了 pexpect.EOF 或 pexpect.TIMEOUT,表示超时或者 EOF,程序打印提示信息并退出.
    print "telnet login failed, due to TIMEOUT or EOF"
    child.close(force=True)


更详细的情况请查看:
https://www.cnblogs.com/mmdln/p/9006274.html
原文地址:https://www.cnblogs.com/linwenbin/p/10944152.html