zoukankan      html  css  js  c++  java
  • python的pexpect模块

    基本使用流程

    pexpect 的使用说来说去,就是围绕3个关键命令做操作:

    1     首先用 spawn 来执行一个程序


    2     使用 expect 来等待指定的关键字,这个关键字是被执行的程序打印到标准输出上面的


    3     最后当发现这个关键字以后,根据关键字用 send 方法来发送字符串给这个程序


    第一步只需要做一次,但在程序中会不停的循环第二、三步来一步一步的完成整个工作。

    掌握这个概念之后 pexpect 的使用就很容易了。当然 pexpect 不会只有这 3 个方法,实际上还有很多外围的其他方法

    # 在本机执行命令,并输出命令执行结果
    import pexpect
    child = pexpect.spawn('ls -l')
    child.expect(pexpect.EOF)
    result = child.before.decode()
    print(result)

    演示的就是spawn()第一个参数command的使用,变量child就是 spawn() 的程序操作句柄了,之后对这个程序的所有操作都是基于这个句柄的,所以它可以说是最重要的部分。
    command参数也可以配合args参数使用:

    child = pexpect.spawn('ls', args = ['-l', '/'])
    child.expect(pexpect.EOF)
    result = child.before.decode()
    print(result)

    注意:command参数不支持直接使用管道,通配符,标志输入,输出,错误重定向,如要使用就必须配合args参数

    child = pexpect.spawn('/bin/bash', ['-c', 'cat test|grep green'])
    child.expect(pexpect.EOF)
    result = child.before.decode()
    print(result)

    timeout参数:设置超时时间,单位为秒


    maxread参数:从TTY读取信息最大缓冲区


    logfile=None:指定日志文件,可指定为sys.stdout


    cwd=None:指定命令运行的目录,默认值 None 或者说 ./


    env=None:命令运行时的环境变量


    encoding=None:命令运行时的编码


    codec_errors=‘strict’:编码转换时的选项

    child = pexpect.spawn('ls -l', logfile=sys.stdout, cwd = '/home')
    child.expect(pexpect.EOF)

    expect()方法

    当 spawn() 启动了一个程序并返回程序控制句柄后,就可以用 expect() 方法来等待指定的关键字了。它最后会返回 0 表示匹配到了所需的关键字,如果后面的匹配关键字是一个列表的话,就会返回一个数字表示匹配到了列表中第几个关键字,从 0 开始计算


    expect() 利用正则表达式来匹配所需的关键字。使用方式如下:

    # pattern_list 正则表达式列表,表示要匹配这些内容
    # timeout 不设置或者设置为-1的话,超时时间就采用self.timeout的值,默认是30秒。也可以自己设置。
    # searchwindowsize 功能和 spawn 上的一样,但是!请注意这个但是!下面会实际说明


    child.expect(pattern_list, timeout=-1, searchwindowsize=None)

    patter_list:可以为字符串,正则表达式,EOF,TIMEOUT,或者以上类型的列表,用以匹配子命令返回的结果。从子命令返回结果中进行匹配,若只提供字符串等非列表,匹配成功返回0;若提供列表,则返回匹配成功的列表序号;匹配失败,抛出异常


    searchwindowsize:是在 expect() 方法中真正生效的,默认情况下是 None,也就是每从子进程中获取一个字符就做一次完整匹配,如果子进程的输出很多的话……性能会非常低。如果设置为其他的值,表示从子进程中读取到多少个字符才做一次匹配,这样会显著减少匹配的次数,增加性能。

    child = pexpect.spawn('ls -l ./')
    child.expect('run')                # 匹配run字符 

    before/after/match:获取程序运行输出


    当 expect() 过程匹配到关键字(或者说正则表达式)之后,系统会自动给3个变量赋值,分别是 before, after 和 match

    before - 保存了到匹配到关键字为止,缓存里面已有的所有数据。也就是说如果缓存里缓存了 100 个字符的时候终于匹配到了关键字,那么 before 就是除了匹配到的关键字之外的所有字符


    after - 保存匹配到的关键字,比如你在 expect 里面使用了正则表达式,那么表达式匹配到的所有字符都在 after 里面


    match - 保存的是匹配到的正则表达式的实例,和上面的 after 相比一个是匹配到的字符串,一个是匹配到的正则表达式实例
    如果 expect() 过程中发生错误,那么 before 保存到目前位置缓存里的所有数据, after 和 match 都是 None

    child = pexpect.spawn('ls -l ./')
    child.expect('run')
    
    print(child.before)
    print(child.match)
    print(child.after)

    expect 方法中也可以传入一个列表,列表中的每个元素都是一个关键字的正则表达式

    child = pexpect.spawn('ls -l ./')
    index = child.expect(['test', 'run'])    # 匹配到列表中任意一个元素即停止匹配
    print(index)                            # 返回列表中匹配到的字符索引

    如果没有匹配到任何字符则抛出异常:

    child = pexpect.spawn('ls -l ./')
    child.expect('who')

    异常信息如下:

    pexpect.exceptions.EOF: End Of File (EOF). Exception style platform.
    <pexpect.pty_spawn.spawn object at 0x7f101f7b4550>
    command: /bin/ls
    args: ['/bin/ls', '-l', './']
    buffer (last 100 chars): ''
    before (last 100 chars): 'rw-r--r-- 1 root root  3731 Mar 20 04:21 run.py
    -rw-r--r-- 1 root root   178 Mar 20 22:36 test.py
    '
    after: <class 'pexpect.exceptions.EOF'>
    match: None
    match_index: None
    exitstatus: 0
    flag_eof: True
    pid: 7785
    child_fd: 5
    closed: False
    timeout: 30
    delimiter: <class 'pexpect.exceptions.EOF'>
    logfile: None
    logfile_read: None
    logfile_send: None
    maxread: 2000
    ignorecase: False
    searchwindowsize: None
    delaybeforesend: 0.05
    delayafterclose: 0.1
    delayafterterminate: 0.1
    searcher: searcher_re:
        0: re.compile('who')

    可以匹配异常,让异常不在终端显示,从而程序不退出运行:

    child = pexpect.spawn('ls -l ./')
    child.expect(pexpect.EOF)            # 如果将此行代码打印的话会输出0
    child = pexpect.spawn('ls')
    child.expect(['run', pexpect.EOF])    # 如果返回1说明匹配到了异常

    匹配时自动应用re.DOTALL正则选项。(.+ 匹配所有字符,.* 返回空字符),匹配行尾使用 ‘ ’,无法用$匹配行尾

    sendline()方法

    sendline() - 发送带回车符的字符串
    sendline() 和 send() 唯一的区别就是在发送的字符串后面加上了回车换行符,这也使它们用在了不同的地方:

    1. 只需要发送字符就可以的话用send()
    2. 如果发送字符后还要回车的话,就用 sendline()

    它也会返回发送的字符数量

    child = pexpect.spawn('nslookup')
    child.expect('>')
    child.sendline('www.baidu.com')
    child.expect('>')
    print(child.before)
    child.sendline('exit')

    其他发送信息的方法

    send() :发送关键字
    send() 作为3个关键操作之一,用来向程序发送指定的字符串,末尾不带回车换行符

    process.expect("ftp>")
    process.send("by
    ")
    # 这个方法会返回发送字符的数量

    sendcontrol():发送控制信号


    sendcontrol() 向子程序发送控制字符,比如 ctrl+C 或者 ctrl+D 之类的,比如你要向子程序发送 ctrl+G,那么就这样写:

    child.sendcontrol('g')

    sendeof() :发送 EOF 信号
    向子程序发送 End Of File 信号,一般用于确认上一次发送内容缓冲结束

    sendintr():发送终止信号
    向子程序发送 SIGINT 信号,相当于 Linux 中的 kill 2 ,它会直接终止掉子进程

    write():发送字符串
    类似于send()命令,只不过不会返回发送的字符数。

    writelines():发送包含字符串的列表
    类似于 write() 命令,只不过接受的是一个字符串列表, writelines() 会向子程序一条一条的发送列表中的元素,但是不会自动在每个元素的最后加上回车换行符。
    与 write() 相似的是,这个方法也不会返回发送的字符数量。

    其他获取结果的方法

    expect_exact():精确匹配
    它的使用和 expect() 是一样的,唯一不同的就是它的匹配列表中不再使用正则表达式。
    从性能上来说 expect_exact() 要更好一些,因为即使你没有使用正则表达式而只是简单的用了几个字符 expect() 也会先将它们转换成正则表达式模式然后再搜索,但 expect_exact() 不会,而且也不会把一些特殊符号转换掉。

    expect_list():预转换匹配
    使用方式和 expect() 一样,唯一不同的就是匹配列表只用已编译正则表达式和EOF, TIMEOUT;提高匹配速度;expect()方法是通过它工作的。
    expect() 稍微有点笨,每调用一次它都会将内部的正则表达式转换一次(当然也有其他办法避免),如果你是在以后循环中调用 expect() 的话,多余的转换动作就会降低性能,在这种情况下建议用 expect_list() 来代替。

    # timeout 为 -1 的话使用 self.timeout 的值
    # searchwindowsize 为 -1 的话,也使用系统默认的值
    child.expect_list(pattern_list, timeout=-1, searchwindowsize=-1)

    expect_loop()
    用于从标准输入中获取内容,loop这个词代表它会进入一个循环,必须要从标准输入中获取到关键字才会往下继续执行。

    expect_loop(self, searcher, timeout=-1, searchwindowsize=-1)

    read():返回剩下的所有内容
    获取子程序返回的所有内容,一般情况下我们可以用 expect 来期待某些内容,然后通过 before 这样的方式来获取,但这种方式有一个前提:那就是必须先 expect 某些字符,然后才能用 before 来获取缓存中剩下的内容。

    read() 的使用很不同,它期待一个 EOF 信号,然后将直到这个信号之前的所有输出全部返回,就像读一个文件那样。
    一般情况下,交互式程序只有关闭的时候才会返回 EOF ,比如用 by 命令关闭 ftp 服务器,或者用 exit 命令关闭一个 ssh 连接。
    这个方法使用范围比较狭窄,因为完全可以用 expect.EOF 方式来代替。当然如果是本机命令,每执行完一次之后都会返回 EOF ,这种情况下倒是很有用:

    child = pexpect.spawn('ls –l')
    output = child.read()
    print output

    可以用指定 read(size=-1) 的方式来设置返回的字符数,如果没有设置或者设置为负数则返回所有内容,正数则返回指定数量的内容,返回的内容是字符串形式。

    readline():返回一行输出
    返回一行输出,返回的内容包括最后的 字符。
    也可以设置 readline(size=-1) 来指定返回的字符数,默认是负数表示返回所有的。

    readlines():返回列表模式的所有输出
    返回一个列表,列表中的每个元素都是一行(包括 字符)。

    1:远程登录主机并执行命令

    child = pexpect.spawn('ssh ginvip@172.17.2.117')
    child.logfile = sys.stdout
    
    child.expect('password')
    child.sendline('ginvip')
    child.expect(
    'ginvip') child.sendline('ls /')
    child.expect(
    'ginvip') child.sendline('exit')

    如果要将日志写入文件:

    child = pexpect.spawn('ssh ginvip@172.17.2.117')
    fout = file('log.txt', 'w')
    child.logfile = fout
    child.expect(
    'password') child.sendline('ginvip')
    child.expect(
    'ginvip') child.sendline('ls /')
    child.expect(
    'ginvip') child.sendline('exit')

    FTP文件管理

    import pexpect, sys, os, re, time
    def login_ftp():
        ftp = pexpect.spawn('ftp', cwd = cwd)
        if ftp.expect(prmpt) != 0:
            sys.exit()
        ftp.sendline('open {ip} {port}'.format(ip = ip, port = port))
        if ftp.expect('Name') != 0:
            sys.exit()
        ftp.sendline(username)
        if ftp.expect('Password:') != 0:
            sys.exit()
        ftp.sendline(password)
        if ftp.expect('230') != 0 or ftp.expect(prmpt) != 0:
            sys.exit()
        return ftp
    
    def get_server_files(ftp):
        ftp.sendline('ls')
        if ftp.expect('226') != 0:
            sys.exit()
        file_list = ftp.before
        file_list = file_list.split('
    ')
        remtch = re.compile('s+')
        file_list = [remtch.subn(' ', item.strip('
    '))[0] for item in file_list if 'group' in item]
        file_dict = dict()
        for item in file_list:
            datas = item.split(' ')
            file_dict[datas[-1]] = {'mon': week.index(datas[-4]) + 1, 'day': int(datas[-3]), 'time': datas[-2]}
        return file_dict
    
    def get_local_files():
        local_files = os.listdir(cwd)
        local_files_dict = dict()
        for file in local_files:
            t = time.ctime(os.stat(os.path.join(cwd, file)).st_mtime)
            datas = t.split(' ')
            local_files_dict[file] = {'mon': week.index(datas[-4]) + 1, 'day': int(datas[-3]), 'time': datas[-2][:5]}
        return local_files_dict
    
    def sync_files(ftp, local_files, remote_files):
        add_file = []
        for file in local_files.keys():
            if file not in remote_files:
                add_file.append(file)
            if file in remote_files:
                if local_files[file]['mon'] > remote_files[file]['mon']:
                    add_file.append(file)
        if add_file:
            for f in add_file:
                ftp.sendline('put' + f)
                if ftp.expect(['226', pexpect.EOF]) == 0:
                    print('upload success: ', f)
                else:
                    sys.exit()
        # 以下代码会删除FTP服务器上的文件,测试请慎重
        # delete_files = set(remote_files.keys()) - set(local_files.keys())
        # if delete_files:
        #     for f in delete_files:
        #         ftp.sendline('delete' + f)
        #         if ftp.expect(['250', pexpect.EOF]) == 0:
        #             print('delete success: ', f)
        #         else:
        #             print('Permision denied')
        #             sys.exit()
    
    def exit_ftp(ftp):
        if ftp:
            ftp.sendcontrol('d')
            print(ftp.read().decode())
    
    if __name__ == '__main__':
        week = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')
        cwd = '/root/backup'
        prmpt = ['ftp>', pexpect.EOF]
        ip = '172.17.2.76'
        port = '2121'
        username = 'xxxxx'
        password = 'xxxxxx'
    
        ftp = login_ftp()
        remote_files = get_server_files(ftp)
        print(remote_files)
        print('=' * 50)
        local_files = get_local_files()
        print(local_files)
        exit_ftp(ftp)

    SSH专用类

    pxssh类:为建立SSH连接定制了一些功能
    第一次连接时可以自动接受远程认证,可以自动应用公钥登录而不用输入密码
    构造方法参数除无CMD参数外其余与spawn类相同

    # 远程登录主机执行命令
    from pexpect.pxssh import pxssh
    hostname = '172.17.2.117'
    user = 'ginvip'
    password = 'ginvip'
    s = pxssh()
    s.login(hostname, user, password)
    s.sendline('ip addr')
    s.prompt()                # 匹配命令提示符
    print(s.before)            # 查看命令执行结果
    s.logout()

    逐个登录指定的多台远程主机,监控远程主机并依据相关信息要求用户处理

    from pexpect.pxssh import pxssh
    import pexpect
    
    def login_host(host):
        ssh = pxssh()
        if ssh.login(host[0], host[1], host[2]):
            return ssh
    
    def get_cpu_num(ssh):
        ssh.sendline('cat /proc/cpuinfo')
        res = ssh.expect(['cpu cores.*
    ', pexpect.EOF])
        if res == 0:
            data = ssh.after.split('
    ')[0]
            data = data[data.index(':') + 1:]
            cpu_cores = int(data)
            ssh.prompt()
            return cpu_cores
    
    def get_cpu_load(ssh):
        ssh.sendline('uptime')
        if ssh.prompt():
            data = ssh.before
            data = data.strip('
    ')
            data = data[data.rfind(':')+1:]
            data = data.split(',')
            return (float(data[0]), float(data[1]), float(data[2]))
    
    def get_cpu_stat(ssh):
        ssh.sendline('vmstat')
        ssh.prompt()
        print(ssh.before)
    
    def user_deal(host, file_name):
        s = login_host(host)
        if not s:
            print('Login failed: ', host[0])
            return
        try:
            cpu_cores = get_cpu_num(s)
            if not cpu_cores:
                print('Do not get cpu cores: ', host[0])
                return
            cpu_load = get_cpu_load(s)
            if cpu_load[2] >= cpu_cores:
                get_cpu_stat(s)
                print('system is not healthy. Do you want to deal? (yes/no)')
                yn = input()
                if yn == 'yes':
                    with open(file_name, 'ab+') as f:
                        s.logfile = f
                        s.interact()
                        s.prompt()
                        s.logfile = None
            else:
                print('system is healthy: ', host[0])
        except Exception as e:
            print('failed: ', host[0])
        finally:
            s.logout()
    
    if __name__ == '__main__':
        hosts = [('172.17.2.117', 'root', 'root')]
        file_name = 'log.txt'
        for host in hosts:
            user_deal(host, file_name)

    以上转自:https://blog.csdn.net/pcn01/article/details/104993742

  • 相关阅读:
    python json.dumps() json.dump()的区别
    geopy 在python中的使用
    socket技术详解(看清socket编程)
    数据结构之各种数据结构插入、删除、查找的时间复杂度
    数组查找的时间复杂度正确表述
    各种排序算法时间复杂度
    MySQL将一张表的某些列数据,复制到另外一张表,并且修改某些内容
    Java虚拟机学习
    Java虚拟机学习
    java集合框架05——ArrayList和LinkedList的区别
  • 原文地址:https://www.cnblogs.com/chengxuyonghu/p/13784189.html
Copyright © 2011-2022 走看看