系統批量運維管理器pexpect詳解

1、pexpect介紹

    pexpect能夠理解成Linux下的expect的Python封裝,經過pexpect咱們能夠實現對ssh、ftp、passwd、telnet等命令進行自動交互,而無需人工干涉來達到自動化的目的。好比咱們能夠模擬一個FTP登錄時的全部交互,包括輸入主機地址、用戶名、密碼、上傳文件等,待出現異常咱們還能夠進行嘗試自動處理。

pexpect官網地址:https://pexpect.readthedocs.io/en/stable/python

        https://pypi.org/project/pexpect/nginx

2、pexpect的安裝

mac os安裝

pip3 install pexpect

CentOS 安裝

[root@localhost ~]# pip3 install pexpect
Collecting pexpect
  Downloading https://files.pythonhosted.org/packages/89/e6/b5a1de8b0cc4e07ca1b305a4fcc3f9806025c1b651ea302646341222f88b/pexpect-4.6.0-py2.py3-none-any.whl (57kB)
    100% |████████████████████████████████| 61kB 128kB/s
Collecting ptyprocess>=0.5 (from pexpect)
  Downloading https://files.pythonhosted.org/packages/ff/4e/fa4a73ccfefe2b37d7b6898329e7dbcd1ac846ba3a3b26b294a78a3eb997/ptyprocess-0.5.2-py2.py3-none-any.whl
Installing collected packages: ptyprocess, pexpect
Successfully installed pexpect-4.6.0 ptyprocess-0.5.2

3、pexpect的核心組件

spawn類

spawn是pexpect的主要類接口,功能是啓動和控制子應用程序,如下是它的構造函數定義:正則表達式

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

其中command參數能夠是任意已知的系統命令,好比:shell

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目錄內容命令

當子程序須要參數時,還可使用Python列表代替參數項,如:windows

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的參數進行調用,例如:bash

child = pexpect.spawn('/bin/bash -c "ls -l | grep LOG > logs.txt"')
child.expect(pexpect.EOF)

咱們能夠經過將命令的參數以Python列表的形式進行替換,從而使咱們的語法變成更加清晰,下面的代碼等價於上面的。ssh

shell_cmd = 'ls -l | grep LOG > logs.txt'
child = pexpect.spawn('/bin/bash', ['-c', shell_cmd])
child.expect(pexpect.EOF)

有時候調式代碼時,但願獲取pexpect的輸入與輸出信息,以便了解匹配的狀況。pexpect提供了兩種途徑,一種爲寫到日誌文件,另外一種爲輸出到標準輸出。寫到日誌文件的方法以下:xss

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

輸出到標準輸出的方法以下:async

# 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 = pexpect.spawn('some_command')
child.logfile_read = sys.stdout

若是您使用的是Python 3,則須要在上面的代碼中傳遞編碼。

要單獨記錄發送給子級的輸出使用logfile_send:

child.logfile_send = fout

注意:若是你想得到孩子的退出狀態,你必須調用close()方法。孩子的出口或信號狀態將存儲在self.exitstatus或self.signalstatus中。若是孩子正常退出,exitstatus將存儲退出返回碼,signalstatus將爲None。若是孩子被信號異常終止,那麼signalstatus將存儲信號值,exitstatus將爲None:

child = pexpect.spawn('some_command')
child.close()
print(child.exitstatus, child.signalstatus)

更多細節,能夠讀取存儲由os.waitpid返回的狀態的self.status成員。你可使用os.WIFEXITED/os.WEXITSTATUS 或 os.WIFSIGNALED/os.TERMSIG來解釋它。

 

下面爲一個完整的示例,實現遠程SSH登陸,登陸成功後顯示/usr/local/src/目錄文件清單,並經過日誌文件記錄全部的輸入與輸出。

import pexpect
import sys

child = pexpect.spawn('ssh root@192.168.56.132')
fout = open('mylog.txt',mode='wb')
child.logfile = fout
#child.logfile = sys.stdout

child.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: 1234567

Last 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

expect方法

expect定義了一個子程序輸出的匹配規則。

方法定義:expect(patterntimeout=-1searchwindowsize=-1async_=False**kw)

其中,參數pattern表示字符串、pexpect.EOF(指向緩衝區尾部,無匹配項)、pexpect.TIMEOUT(匹配等待超時)、正則表達式或者前面四種類型組成的列表(List),當pattern爲一個列表時,且不止一個列表元素被匹配,則返回的結果是子程序輸出最早出現的那個元素,或者是列表最左邊的元素(最小索引ID),如:

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方法有兩個很是棒的成員:befoe與。before成員保存了最近匹配成功以前的內容,affer成員保存了最近匹配成功以後的內容。例如:

import pexpect


child = pexpect.spawn('ssh root@192.168.56.132',encoding='utf-8')
fout = open('mylog.txt',mode='w')
child.logfile = fout

child.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:

read相關方法

下面這些輸入方法的做用都是向子程序發送響應命令,能夠理解成代替了咱們的標準輸入鍵盤。

send(self,s)   發送命令,不回車
sendline(self, s='')   發送命令,回車
sendcontrol(self, char)   發送控制字符,如child.sendcontrol('c')等價於"ctrl+c"
sendof()       發送eof

4、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')
child.sendline(mypassword)

使用run函數實現以下,是否是更加簡潔、精煉了?

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

pxssh類

該類將pexpect.spawn擴展爲專門設置SSH鏈接。增長了登陸,註銷和指望的shell提示的方法。它執行各類棘手的事情來處理SSH登陸過程當中的許多狀況。例如,若是會話是您第一次登陸,則pxssh會自動接收遠程證書;或者你有公鑰認證設置,則pxssh不會等待密碼提示。
pxssh使用shell提示符來同步來自遠程主機的輸出。爲了使它更健壯,它將shell提示設置爲比$或#更獨特的東西。這應該適用於大多數Borne/Bash or Csh 風格的 shell。

pxssh類定義:

class(timeout=30maxread=2000searchwindowsize=Nonelogfile=Nonecwd=Noneenv=Noneignore_sighup=Trueecho=Trueoptions={}encoding=Nonecodec_errors='strict'debug_command_string=False)pexpect.pxssh.pxssh

pxssh經常使用的三個方法以下:

  • login()創建ssh鏈接;
  • logout()斷開鏈接;
  • prompt()等待系統提示符,用於等待命令執行結束。

下面使用pxssh類實現一個ssh鏈接遠程主機並執行命令的示例。首先使用login()方法與遠程主機創建鏈接,再經過sendline()方法發送執行的命令,prompt()方法等待命令執行結束且出現系統提示符,最後使用logout()方法斷開鏈接。

from pexpect import pxssh
import getpass
try:
    s = pxssh.pxssh()       #建立pxssh對象s
    hostname = 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)

5、pexpect應用實例

遠程文件自動打包並下載

import pexpect
import sys

ip='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 = fout

try:
    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')

child = pexpect.spawn('/usr/bin/scp', [user+'@'+ip+':/data/nginx_access.tar.gz','/home'],encoding='utf-8')  #啓動scp遠程拷貝命令,實現將打包好的nginx日誌複製到本地/home目錄下
fout = open('mylog.txt','a')
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')
相關文章
相關標籤/搜索