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

一、 pexpect的安装

支持三种安装方式:pip安装、easy_install安装、源码安装

pip安装

pip install pexpect

easy_install安装

easy_install pexpect

源码安装

tar -zxvf pexpect-3.0.tar.gz
cd pexpect-3.0
python setup.py install

一个简单的自动scp示例如下:

import pexpect
#spawn启动scp程序
child=pexpect.spawn('scp file1 oracle@192.168.3.9:/home/oracle')
#expect方法等待scp产生的输出,判断是否匹配指定的字符串Password:
child.expect('Password:')
#若匹配,则发送密码响应
mypassword='xxxxx'
child.sendline(mypassword)

pexpect核心组件主要包括:spawn类、run函数、派生类pxssh等,下面介绍它们的定义及使用方法。

二、 spawn类

spawn是pexpect的主要类接口(windows无法运行),功能是启动和控制子应用程序,以下是它的构造函数:

class pexpect.spawn(command,args=[],timeout=30,maxread=2000,searchwindowsize=None,logfile=None,cwd=None,env=None,ignore_sighup=True)

1. 参数说明

  • command:可以是任意Linux系统命令,比如
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目录内容命令
  • args[]:当命令需要参数时,还可以使用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)

2. 获取pexpect对象信息

有时调试代码时,希望获取pexpect的输入与输出信息,以便了解匹配的情况。pexpect提供了两种途径:写到日志文件或标准输出。

写到日志文件的方法如下:

child = pexpect.spawn('some_command')
fout = open('mylog.txt','wb')
child.logfile = fout

写到标准输出的方法如下:

# In Python 2:
child = pexpect.spawn('some_command')
child.logfile = sys.stdout# In Python 3, we'll use the ``encoding`` argument to decode data
# from the subprocess and handle it as unicode:
child = pexpect.spawn('some_command', encoding='utf-8')
child.logfile = sys.stdout

logfile_read和logfile_send成员分别记录child接收和发送的内容。

有时你不想关注发送给child的内容,可以只输出child接收的内容。

child = pexpect.spawn('some_command', encoding='utf-8')
child.logfile_read = sys.stdout

如果想获得child的退出状态,必须调用close()方法。child的退出或信号状态将存储在self.exitstatus或self.signalstatus中。

  • 如果正常退出,exitstatus将存储退出返回码,signalstatus将为None。
  • 如果被信号异常终止,那么signalstatus将存储信号值,exitstatus将为None。
child = pexpect.spawn('some_command')
child.close()
print(child.exitstatus, child.signalstatus)

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

import pexpect
import syschild = pexpect.spawn('ssh root@192.168.56.132')
fout = open('mylog.txt',mode='wb')
child.logfile = fout
#child.logfile = sys.stdoutchild.expect("(yes/no)?")
child.sendline("yes")
child.expect("password:")
child.sendline("1234567")
child.expect('#')
child.sendline('/bin/ls /usr/local/src/')
child.expect("#")

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

[root@localhost ~]# cat mylog.txt
yes
yes
root@192.168.56.132's password: 1234567Last login: Sat Jun  2 15:13:51 2018 from 192.168.56.131
[root@localhost ~]# /bin/ls /usr/local/src/
/bin/ls /usr/local/src/
Python-3.6.5  Python-3.6.5.tgz

3. expect方法

expect定义了子程序输出的匹配规则,方法定义如下:

expect(pattern,timeout=-1,searchwindowsize=-1,async_=False,**kw)

参数pattern可由以下几种类型组成

  • 字符串
  • pexpect.EOF(指向缓冲区尾部,无匹配项)
  • pexpect.TIMEOUT(匹配等待超时)
  • 正则表达式
  • 前面四种类型组成的列表(List)

当pattern为列表,且不止一个列表元素被匹配时,返回的结果是子程序输出最先出现的元素,或者是列表最左边的元素,如:

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成员保存了最近匹配成功之前的内容,affer成员保存了最近匹配成功之后的内容。例如:

import pexpectchild = pexpect.spawn('ssh root@192.168.56.132',encoding='utf-8')
fout = open('mylog.txt',mode='w')
child.logfile = foutchild.expect("(yes/no)?")
child.sendline("yes")
child.expect(['password:'])
child.sendline("1234567")
print("before:"+child.before)
print("after:"+child.after)

运行结果如下:

[root@localhost ~]# python3 simple2.py
before:yes
root@192.168.56.132's
after:password:

4. read相关方法

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

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

5. 更简洁的实现方法——run

run是使用pexpect进行封装的调用外部命令的函数,类似于os.system或os.popen方法,不同的是使用run()可以同时获得命令的输出结果及命令的退出状态。

函数定义:

pexpect.run(command, timeout=30, withexitstatus=False, events=None, extra_args=None, logfile=None, cwd=None, env=None, **kwargs)
  • command可以是系统的任意命令,没有写绝对路径时将会尝试搜索命令的路径
  • events是一个字典,定义了expect及sendline方法的对应关系

下面看一个实现对比,spawn方式如下:

from pexpect import *
child = spawn('scp foo user@example.com:.')
child.expect('(?i)password')  # (?i)表示后面的字符串正则匹配忽略大小写
child.sendline(mypassword)

使用run函数实现如下,更加简洁精炼了

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

三、 spawn的扩展——pxssh类

pxssh是pexpect的派生类,它将pexpect.spawn扩展为专门设置SSH连接,增加了登录,注销和期望的shell提示的方法。

针对在ssh会话操作上再进行一层封装,提供与基类更直接的操作方法。

1. pxssh类定义

pexpect.pxssh.pxssh(timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None, ignore_sighup=True, echo=True, options={}, encoding=None, codec_errors='strict', debug_command_string=False)

2. pxssh常用的三个方法

  • login():建立ssh连接;
  • logout():断开连接;
  • prompt():等待系统提示符,用于等待命令执行结束。

下面使用pxssh类实现一个ssh连接远程主机并执行命令的示例。

首先使用login()方法与远程主机建立连接,再通过sendline()方法发送执行的命令,prompt()方法等待命令执行结束且出现系统提示符,最后使用logout()方法断开连接。

from pexpect import pxssh
import getpass
try:s = pxssh.pxssh()       #创建pxssh对象shostname = raw_input('hostname: ')username = raw_input('username: ')password = getpass.getpass('password: ')         #接收密码输入s.login(hostname, username, password)            #建立ssh连接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()               #断开ssh连接
except pxssh.ExceptionPxssh as e:print("pxssh failed on login.")print(e)

四、pexpect应用实例

1. 实现自动化FTP操作

我们常用FTP协议实现自动化、集中式的文件备份,要求做到账号登录,文件上传、下载、退出等实现自动化操作。

from __future__ import unicode_literals  # 使用unicode编码import pexpect
import sys# 运行ftp命令
child = pexpect.spawn('ftp 192.168.0.132')# (?i)表示后面的字符串正则匹配忽略大小写
child.expect('(?i)name .*: ')
# 输入ftp账号信息
child.sendline('账号')# 匹配密码输入提示
child.expect('(?i)password')
# 输入ftp密码
child.sendline('密码')child.expect('ftp> ')
child.sendline('bin') # 启用二进制传输模式child.expect('ftp> ')
child.sendline('get robot.txt') # 下载robots.txtchild.expect('ftp> ')
# 输出匹配的ftp > 之前的输入与输出
sys.stdout.write(child.before.decode('utf-8'))
print("Escape character is '^]'. \n")
sys.stdout.write(child.after.decode('utf-8'))
sys.stdout.flush()# 调用interact让出控制权,用户可以继续当前的会话手工控制子程序,默认输入“^]”字符跳出
child.interact()
child.sendline('bye')
child.close()

运行结果如下:

最后 ftp>  是调用interact()控制项让出,用户可以手工交互

2. 远程文件自动打包并下载

本示例通过使用spawn()方法执行ssh、scp命令的思路实现。

import pexpectip='192.168.56.132'         #定义目标主机
user='root'                 #目标主机用户
passwd='1234567'          #目标主机密码
target_file='/data/logs/nginx_access.log'           #目标主机nginx日志文件child = pexpect.spawn('/usr/bin/ssh', [user+'@'+ip],encoding='utf-8')       #运行ssh命令
fout = open('mylog.txt','w')                                #输入、输出日志写入mylog.txt文件
child.logfile = fouttry:child.expect('(?i)password')            #匹配password字符串,(?i)表示不区别大小写child.sendline(passwd)child.expect('#')child.sendline('tar -zcPf /data/nginx_access.tar.gz ' +target_file)       #打包nginx日志文件child.expect('#')print(child.before)child.sendline('exit')fout.close()
except EOFError:       #定义EOF异常处理print('expect EOF')
except TimeoutError:        #定义timeout异常处理print('expect timeout')fout = open('mylog.txt','a')
#启动scp远程拷贝命令,实现将打包好的nginx日志复制到本地/home目录下
child = pexpect.spawn('/usr/bin/scp', [user+'@'+ip+':/data/nginx_access.tar.gz','/home'],encoding='utf-8')
child.logfile = fout
try:child.expect('(?i)password')child.sendline(passwd)child.expect(pexpect.EOF)      #匹配缓冲区EOF(结尾),保证文件复制正常完成
except EOFError:print('expect EOF')
except TimeoutError:print('expect timeout')

3. 完整实现ftp登陆,下载文件到本地

# coding=UTF-8
import pexpect# 即将 ftp 所要登录的远程主机的域名
ipAddress = '192.168.0.132'# 登录用户名
loginName = 'root'# 用户名密码
loginPassword = '12580'# 拼凑 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 ftptest.txt")# 期望下载成功后,出现 '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)

输出结果:

4. ssh登陆并执行命令,获取命令返回结果

#coding=utf-8
"""
This runs a command on a remote host using SSH. At the prompts enter hostname,
user, password and the command.
"""import pexpect
import getpass
import os
import traceback#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 thepxssh 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 toconnect to a new host and ssh asks you if you want to accept the public keyfingerprint 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: # Timeoutprint('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: # Timeoutprint('ERROR!')print('SSH could not login. Here is what SSH said:')print(child.before, child.after)return None# 输入密码.child.sendline(password)return childif i == 2:# 输入密码.child.sendline(password)return childdef main ():# 获得用户指定 ssh 主机域名.host = input('Hostname: ')# 获得用户指定 ssh 主机用户名.user = input('User: ')# 获得用户指定 ssh 主机密码.password = getpass.getpass()# 获得用户指定 ssh 主机上即将运行的命令.command = input('Enter the command: ')child = ssh_command(user, host, password, command)# 匹配 pexpect.EOFchild.expect(pexpect.EOF,timeout=180)# 输出命令结果.print(child.before.strip().decode('utf-8'))if __name__ == '__main__':try:main()except Exception:print("ERROR!")traceback.print_exc()os._exit(1)

执行结果:

参考:

Pexpect 的实例分析 - OSCHINA - 中文开源技术交流社区

8.python 系统批量运维管理器之pexpect模块 - bmjoker - 博客园

Pexpect 的实例分析 - OSCHINA - 中文开源技术交流社区

python自动化运维——系统批量运维管理器pexpect详解相关推荐

  1. 系统批量运维管理器Fabric详解

    系统批量运维管理器Fabric详解 Fabrici 是基于python现实的SSH命令行工具,简化了SSH的应用程序部署及系统管理任务,它提供了系统基础的操作组件,可以实现本地或远程shell命令,包 ...

  2. python修改文件内容_Python批量修改文本文件内容的方法详解

    这篇文章主要介绍了Python批量修改文本文件内容的方法的相关资料,需要的朋友可以参考下 Python批量替换文件内容,支持嵌套文件夹 import os path="./" fo ...

  3. python sqlsever 时间_Python sqlalchemy时间戳及密码管理实现代码详解

    一.时间戳 实际开发中,我们一般希望create_time和update_time这两个属性能自动设置,所以在建表的时候需要在model上特殊处理一下: from sqlalchemy.sql imp ...

  4. 服务器系统2012设置,Windows Server 2012服务器管理器图文详解

    作为技术人员来说,我们对Windows Server的关注始终是在系统本身,包括能不能够更好管理好服务器,或者在功能方面更加强大.操作上更加简单.在Windows Server 2012中服务器管理器 ...

  5. 非常运维 一体化终端安全管理系统自动安装脚本详解

    非常运维 一体化终端安全管理系统自动安装脚本详解   作者:高玉涵 时间:2019.03.13 13:52 博客:blog.csdn.net/cg_i 演示:https://v.youku.com/v ...

  6. python怎么安装myqr模块-python二维码操作:对QRCode和MyQR入门详解

    python是所有编程语言中模块最丰富的 生活中常见的二维码功能在使用python第三方库来生成十分容易 三个大矩形是定位图案,用于标记二维码的大小.这三个定位图案有白边,通过这三个矩形就可以标识一个 ...

  7. python怎么安装myqr_python二维码操作:对QRCode和MyQR入门详解

    python是所有编程语言中模块最丰富的 生活中常见的二维码功能在使用python第三方库来生成十分容易 三个大矩形是定位图案,用于标记二维码的大小.这三个定位图案有白边,通过这三个矩形就可以标识一个 ...

  8. Python教程:python中二维列表的创建、访问、应用详解

    欢迎你来到站长在线的站长学堂学习Python知识,本文学习的是<Python中二维列表的创建.访问.应用详解>.本知识点主要内容有:二维列表的概念.直接定义二维列表.使用嵌套的for循环创 ...

  9. Python模块之pexpect详解

    Python模块之pexpect详解(一) 一.pexpect模块介绍 二.Pexpect的安装 三.pexpect的核心组件 3.1 spawn类 3.1.1 简介 3.1.2 使用流程 3.1.3 ...

最新文章

  1. web中的各种打印方案
  2. C语言数组作为传入参数
  3. sob攻略超详细攻略_重庆旅游攻略~超详细攻略大全!必看篇!!
  4. 如何让在JAVA中定义常量池
  5. uva 11383(二分图最大权匹配)
  6. TC第一次成为room leader
  7. 如果要在mFC客户区添加控件怎么办
  8. shell制表与脚本运行进度条写法
  9. 【POJ】2823 Sliding Window
  10. 同花顺数据同步到mysql_同花顺数据库怎么购买,同花顺ifind账号
  11. BeX5安装遇到问题
  12. pc版Web聊天界面+代码分享(HTML+CSS)
  13. 静态测试和动态测试(面试题)
  14. C学习笔记——(4)数组和字符串说明,以及冒泡排序法
  15. 隐藏在《王者荣耀》中程序24种设计模式
  16. 深圳高交会ZDNS发布域名服务安全威胁管控系统,为互联网入口保驾护航
  17. 百度地图SDK,报167错误,经纬度定位是4.9E-324的解决办法
  18. podcast什么意思php,什么是podcast?
  19. Linux网络与数据封装
  20. 我学会了用计算机作文,我学会了用计算机

热门文章

  1. 单片机温度控制系统课程设计
  2. Photoshop CS6去除数码照片画面中的噪点实例详解教程
  3. python中time库引用不正确的_time库的引用
  4. 近几年天猫双十一交易额数据是多少
  5. 如何卸载一个操作系统-以卸载Linux Deepin为例
  6. php cgminer,CGMINER中各个参数代表的意义(挖矿黑框参数)
  7. CSS中有哪些隐藏页面元素的方法?
  8. 使用员工管理软件,解锁人力生产力新水平,提高人力资源团队灵活性
  9. 用Taurus.MVC 做个企业站(下)
  10. Walgreens以myWalgreens重塑美国最大的卫生健康忠诚度计划,为顾客带来更加丰富的福利