Python之pexpect詳解

1、引子

Pexpect程序主要用於人機對話的模擬,就是那種系統提問,人來回答yes/no,或者帳號登錄輸入用戶名和密碼等等的狀況。由於這種狀況特別多並且繁瑣,因此不少語言都有各類本身的實現。最初的第一個 Expect 是由 TCL 語言實現的,因此後來的 Expect 都大體參考了最初的用法和流程,總體來講大體的流程包括:python

  • 運行程序
  • 程序要求人的判斷和輸入
  • Expect 經過關鍵字匹配
  • 根據關鍵字向程序發送符合的字符串

TCL 語言實現的 Expect 功能很是強大,我曾經用它實現了防火牆設備的完整測試平臺。也由於它使用方便、範圍廣,幾乎全部腳本語言都實現了各類各樣的相似與Expect的功能,它們叫法雖然不一樣,但原理都相差不大linux

pexpect 是 Python 語言的類 Expect 實現。從個人角度來看,它在功能上與 TCL 語言的實現仍是有一些差距,好比沒有buffer_full 事件、好比沒有 expect before/after 事件等,但用來作通常的應用仍是足夠了。正則表達式

2、基本使用流程

pexpect的使用說來講去,就是圍繞3個關鍵命令作操做:shell

  • 首先用spawn來執行一個程序
  • 而後用expect來等待指定的關鍵字,這個關鍵字是被執行的程序打印到標準輸出上面的
  • 最後當發現這個關鍵字之後,根據關鍵字用send方法來發送字符串給這個程序

第一步只須要作一次,但在程序中會不停的循環第2、三步來一步一步的完成整個工做。掌握這個概念以後 pexpect 的使用就很容易了。固然 pexpect 不會只有這 3 個方法,實際上還有不少外圍的其餘方法,咱們一個一個來講明windows

3、API

spawn()-執行程序

spawn()方法用來執行一個程序,它返回這個程序的操做句柄,之後能夠經過操做這個句柄來對這個程序進行操做:api

process = pexpect.spawn('df -h')

process 就是 spawn() 的程序操做句柄了,以後對這個程序的全部操做都是基於這個句柄的,因此它能夠說是最重要的部分。儘可能給它起個簡短點的名字,否則後面的程序要多打很多字的。-緩存

注意: spawn() ,或者說 pexpect 並不會轉譯任何特殊字符 好比 | * 字符在Linux的shell中有特殊含義,可是在 pexpect 中不會轉譯它們,若是在 linux 系統中想使用這些符號的正確含義就必須加上 shell 來運行,這是很容易犯的一個錯誤。ssh

正確的方式:函數

import pexpect

process = pexpect.spawn('df -h')
print(process.expect(pexpect.EOF))   # 打印index

timeout - 超時時間

默認值:30(單位:秒)性能

指定程序的默認超時時間。程序被啓動以後會輸出,咱們也會在腳本中檢查出中的關鍵字是不是以知並處理的,若是指定時間內沒找到程序就會出錯返回。

maxread - 緩存設置

默認值:2000(單位:字符)

指定一次性試着從命令輸出中讀多少數據。若是設置的數字比較大,那麼從 TTY 中讀取數據的次數就會少一些。

設置爲 1 表示關閉讀緩存。

設置更大的數值會提升讀取大量數據的性能,但會浪費更多的內存。這個值的設置與 searchwindowsize 合做會提供更多功能。

緩存的大小並不會影響獲取的內容,也就是說若是一個命令輸出超過2000個字符之後,先前緩存的字符不會丟失掉,而是放到其餘地方去,當你用 self.before (這裏 self 表明 spawn 的實例)仍是能夠取到完整的輸出的。

searchwindowsize - 模式匹配閥值

默認值: None

searchwindowsize 參數是與 maxread 參數一塊兒合做使用的,它的功能比較微妙,但能夠顯著減小緩存中有不少字符時的匹配時間。

默認狀況下, expect() 匹配指定的關鍵字都是這樣:每次緩存中取得一個字符時就會對整個緩存中的全部內容匹配一次正則表達式,你能夠想像若是程序的返回特別多的時候,性能會多麼的低。

設置 searchwindowsize 的值表示一次性收到多少個字符以後才匹配一次表達式,好比如今有一條命令會出現大量的輸出,但匹配關鍵字是標準的 FTP 提示符 ftp> ,顯然要匹配的字符只有 5 個(包括空格),可是默認狀況下每當 expect 得到一個新字符就從頭匹配一次這幾個字符,若是緩存中已經有了 1W 個字符,一次一次的從裏面匹配是很是消耗資源的,這個時候就能夠設置 searchwindowsize=10, 這樣 expect 就只會從最新的(最後獲取的) 10 個字符中匹配關鍵字了,若是設置的值比較合適的話會顯著提高性能。不用擔憂緩存中的字符是否會被丟棄,無論有多少輸出,只要不超時就總會獲得全部字符的,這個參數的設置僅僅影響匹配的行爲。

這個參數通常在 expect() 命令中設置, pexpect 2.x 版本彷佛有一個 bug ,在 spawn 中設置是不生效的。

logfile - 運行輸出控制

默認值: None

當給 logfile 參數指定了一個文件句柄時,全部從標準輸入和標準輸出得到的內容都會寫入這個文件中(注意這個寫入是 copy 方式的),若是指定了文件句柄,那麼每次向程序發送指令(process.send)都會刷新這個文件(flush)。

這裏有一個很重要的技巧:若是你想看到spawn過程當中的輸出,那麼能夠將這些輸出寫入到 sys.stdout 裏去,好比:

process = pexpect.spawn("ftp sw-tftp", logfile=sys.stdout)

用這樣的方式能夠看到整個程序執行期間的輸入和輸出,很適合調試。

還有一個例子:

process = pexpect.spawn("ftp sw-tftp")
logFileId = open("logfile.txt", 'w')
process.logfile = logFileId

注意: logfile.txt 文件裏,既包含了程序運行時的輸出,也包含了 spawn 向程序發送的內容,有的時候你也許不但願這樣,由於某些內容出現了2次,那麼還有 2 個很重要的 logfile 關聯參數:

logfile_read - 獲取標準輸出的內容

默認值: None

記錄執行程序中返回的全部內容,也就是去掉你發出去的命令,而僅僅只包括命令結果的部分:

process.logfile_read = sys.stdout

上面的語句會在屏幕上打印程序執行過程當中的全部輸出,可是通常不包含你向程序發送的命令,不過大部分程序都有回顯機制,好比發命令的時候設備不光接收到命令字符串,還會反向在你的終端上把字符串顯示出來讓你明白哪些字符被輸入了,這種時候也是會被這個方法讀到的。只有那些不會回顯的狀況 logfile_read 纔會拿不到,好比輸入密碼的時候。

logfile_send - 獲取發送的內容
默認值: None

記錄向執行程序發送的全部內容

process.logfile_send = sys.stdout

4、pexpect實現ssh操做

# -*- coding: utf-8 -*-
#!/usr/bin/python

import pexpect

def login_ssh_password(port,user,host,passwd):
    '''函數:用於實現pexpect實現ssh的自動化用戶密碼登陸'''
    if  port and user and host and passwd:
        ssh = pexpect.spawn('ssh -p %s %s@%s' % (port,user, host))
        i = ssh.expect(['password:', 'continue connecting (yes/no)?'], timeout=5)
        if i == 0 :
            ssh.sendline(passwd)
        elif i == 1:
            ssh.sendline('yes\n')  # 交互認證
            ssh.expect('password: ')
            ssh.sendline(passwd)
        index = ssh.expect (["#", pexpect.EOF, pexpect.TIMEOUT])
        if index == 0:
            print("logging in as root!")
            ssh.interact()
        elif index == 1:
            print("logging process exit!")
        elif index == 2:
            print("logging timeout exit")
    else:
        print("Parameter error!")

def login_ssh_key(keyfile,user,host,port):
    '''函數:用於實現pexpect實現ssh的自動化密鑰登陸'''

    if  port and user and host and keyfile:
        ssh = pexpect.spawn('ssh -i %s -p %s %s@%s' % (keyfile,port,user, host))
        i = ssh.expect( [pexpect.TIMEOUT,'continue connecting (yes/no)?'], timeout=2)
        if i == 1:
            ssh.sendline('yes\n')
            index = ssh.expect (["#", pexpect.EOF, pexpect.TIMEOUT])
        else:
            index = ssh.expect (["#", pexpect.EOF, pexpect.TIMEOUT])
        if index == 0:
            print("logging in as root!")
            ssh.interact()
        elif index == 1:
            print("logging process exit!")
        elif index == 2:
            print("logging timeout exit")
    else:
        print("Parameter error!")

def main():
    '''主函數:實現兩種方式分別的登陸'''
    login_ssh_password('22','root','10.211.55.12','admin')
    # login_ssh_key(keyfile="/tmp/id_rsa",port='22',user='root',host='192.168.1.101')


if __name__ == "__main__":
    main()
相關文章
相關標籤/搜索