1. 程式人生 > >系統批量運維管理器pexpect詳解

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

utf-8 roo shel last ack 客戶端 all signal 管道

一、pexpect介紹

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

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

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

二、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

三、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參數可以是任意已知的系統命令,比如:

child = pexpect.spawn(/usr/bin/ftp)      #啟動FTP客戶端命令
child = pexpect.spawn(/usr/bin/ssh [email protected])    #啟動ssh遠程連接命令
child = pexpect.spawn(ls -latr /tmp)    #運行ls顯示/tmp目錄內容命令

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

child = pexpect.spawn(/usr/bin/ftp, [])
child = pexpect.spawn(/usr/bin/ssh, [[email protected]])
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)

有時候調式代碼時,希望獲取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, well 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 [email protected])
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.132s 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(pattern, timeout=-1, searchwindowsize=-1, async_=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 [email protected],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.132s
after:password:

read相關方法

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

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

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