欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

對(duì)Python Pexpect 模塊的使用說(shuō)明詳解

 更新時(shí)間:2019年02月14日 09:20:44   作者:hNicholas  
今天小編就為大家分享一篇對(duì)Python Pexpect 模塊的使用說(shuō)明詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧

背景介紹

Expect 程序主要用于人機(jī)對(duì)話(huà)的模擬,就是那種系統(tǒng)提問(wèn),人來(lái)回答 yes/no ,或者賬號(hào)登錄輸入用戶(hù)名和密碼等等的情況。因?yàn)檫@種情況特別多而且繁瑣,所以很多語(yǔ)言都有各種自己的實(shí)現(xiàn)。最初的第一個(gè) Expect 是由 TCL 語(yǔ)言實(shí)現(xiàn)的,所以后來(lái)的 Expect 都大致參考了最初的用法和流程,整體來(lái)說(shuō)大致的流程包括:

運(yùn)行程序

程序要求人的判斷和輸入

Expect 通過(guò)關(guān)鍵字匹配

根據(jù)關(guān)鍵字向程序發(fā)送符合的字符串

TCL 語(yǔ)言實(shí)現(xiàn)的 Expect 功能非常強(qiáng)大,我曾經(jīng)用它實(shí)現(xiàn)了防火墻設(shè)備的完整測(cè)試平臺(tái)。也因?yàn)樗褂梅奖恪⒎秶鷱V,幾乎所有腳本語(yǔ)言都實(shí)現(xiàn)了各種各樣的類(lèi)似與Expect的功能,它們叫法雖然不同,但原理都相差不大

pexpect 是 Python 語(yǔ)言的類(lèi) Expect 實(shí)現(xiàn)。從我的角度來(lái)看,它在功能上與 TCL 語(yǔ)言的實(shí)現(xiàn)還是有一些差距,比如沒(méi)有buffer_full 事件、比如沒(méi)有 expect before/after 事件等,但用來(lái)做一般的應(yīng)用還是足夠了。

基本使用流程

pexpect 的使用說(shuō)來(lái)說(shuō)去,就是圍繞3個(gè)關(guān)鍵命令做操作:

首先用 spawn 來(lái)執(zhí)行一個(gè)程序

然后用 expect 來(lái)等待指定的關(guān)鍵字,這個(gè)關(guān)鍵字是被執(zhí)行的程序打印到標(biāo)準(zhǔn)輸出上面的

最后當(dāng)發(fā)現(xiàn)這個(gè)關(guān)鍵字以后,根據(jù)關(guān)鍵字用 send 方法來(lái)發(fā)送字符串給這個(gè)程序

第一步只需要做一次,但在程序中會(huì)不停的循環(huán)第二、三步來(lái)一步一步的完成整個(gè)工作。掌握這個(gè)概念之后 pexpect 的使用就很容易了。當(dāng)然 pexpect 不會(huì)只有這 3 個(gè)方法,實(shí)際上還有很多外圍的其他方法,我們一個(gè)一個(gè)來(lái)說(shuō)明

API

spawn() - 執(zhí)行程序

spawn() 方法用來(lái)執(zhí)行一個(gè)程序,它返回這個(gè)程序的操作句柄,以后可以通過(guò)操作這個(gè)句柄來(lái)對(duì)這個(gè)程序進(jìn)行操作,比如:

process = pexpect.spawn('ftp sw-tftp')

上面 spawn() 中的字符串就是要執(zhí)行的程序,這里我們打開(kāi)一個(gè)到 sw-tftp 服務(wù)器的 ftp 連接。 spawn() 中的第一個(gè)元素就是要執(zhí)行的命令,除此之外還可以指定一些其他參數(shù),比如: pexpect.spawn('ftp sw-tftp', timeout=60) 就指定了超時(shí)時(shí)間,這些具體的會(huì)在后面講解。

process 就是 spawn() 的程序操作句柄了,之后對(duì)這個(gè)程序的所有操作都是基于這個(gè)句柄的,所以它可以說(shuō)是最重要的部分。盡量給它起個(gè)簡(jiǎn)短點(diǎn)的名字,不然后面的程序要多打不少字的。-

注意: spawn() ,或者說(shuō) pexpect 并不會(huì)轉(zhuǎn)譯任何特殊字符 比如 | * 字符在Linux的shell中有特殊含義,但是在 pexpect 中不會(huì)轉(zhuǎn)譯它們,如果在 linux 系統(tǒng)中想使用這些符號(hào)的正確含義就必須加上 shell 來(lái)運(yùn)行,這是很容易犯的一個(gè)錯(cuò)誤。

正確的方式:

process = pexpect.spawn('/bin/bash –c "ls –l | grep LOG > log_list.txt"')
process.expect(pexpect.EOF)

spawn() 還有一種調(diào)用方式就是第一個(gè)參數(shù)是主程序,而下一個(gè)參數(shù)是主程序的參數(shù),理解起來(lái)很麻煩?看看實(shí)際代碼吧:

cmd = "ls –l | grep LOG > log_list.txt"
process = pexpect.spawn("/bin/bash", ["-c", cmd])
process.expect(pexpect.EOF)

這些代碼和上面一個(gè)例子是相同的,是不是更清晰一些?

spawn 的選項(xiàng)包括下面這些:

timeout - 超時(shí)時(shí)間

默認(rèn)值: 30 (單位:秒)

指定程序的默認(rèn)超時(shí)時(shí)間。程序被啟動(dòng)之后會(huì)有輸出,我們也會(huì)在腳本中檢查輸出中的關(guān)鍵字是否是已知并處理的,如果指定時(shí)間內(nèi)沒(méi)找到程序就會(huì)出錯(cuò)返回。

maxread - 緩存設(shè)置

默認(rèn)值: 2000 (單位:字符)

指定一次性試著從命令輸出中讀多少數(shù)據(jù)。如果設(shè)置的數(shù)字比較大,那么從 TTY 中讀取數(shù)據(jù)的次數(shù)就會(huì)少一些。

設(shè)置為 1 表示關(guān)閉讀緩存。

設(shè)置更大的數(shù)值會(huì)提高讀取大量數(shù)據(jù)的性能,但會(huì)浪費(fèi)更多的內(nèi)存。這個(gè)值的設(shè)置與 searchwindowsize 合作會(huì)提供更多功能。

緩存的大小并不會(huì)影響獲取的內(nèi)容,也就是說(shuō)如果一個(gè)命令輸出超過(guò)2000個(gè)字符以后,先前緩存的字符不會(huì)丟失掉,而是放到其他地方去,當(dāng)你用 self.before (這里 self 代表 spawn 的實(shí)例)還是可以取到完整的輸出的。

searchwindowsize - 模式匹配閥值

默認(rèn)值: None

searchwindowsize 參數(shù)是與 maxread 參數(shù)一起合作使用的,它的功能比較微妙,但可以顯著減少緩存中有很多字符時(shí)的匹配時(shí)間。

默認(rèn)情況下, expect() 匹配指定的關(guān)鍵字都是這樣:每次緩存中取得一個(gè)字符時(shí)就會(huì)對(duì)整個(gè)緩存中的所有內(nèi)容匹配一次正則表達(dá)式,你可以想像如果程序的返回特別多的時(shí)候,性能會(huì)多么的低。

設(shè)置 searchwindowsize 的值表示一次性收到多少個(gè)字符之后才匹配一次表達(dá)式,比如現(xiàn)在有一條命令會(huì)出現(xiàn)大量的輸出,但匹配關(guān)鍵字是標(biāo)準(zhǔn)的 FTP 提示符 ftp> ,顯然要匹配的字符只有 5 個(gè)(包括空格),但是默認(rèn)情況下每當(dāng) expect 獲得一個(gè)新字符就從頭匹配一次這幾個(gè)字符,如果緩存中已經(jīng)有了 1W 個(gè)字符,一次一次的從里面匹配是非常消耗資源的,這個(gè)時(shí)候就可以設(shè)置 searchwindowsize=10, 這樣 expect 就只會(huì)從最新的(最后獲取的) 10 個(gè)字符中匹配關(guān)鍵字了,如果設(shè)置的值比較合適的話(huà)會(huì)顯著提升性能。不用擔(dān)心緩存中的字符是否會(huì)被丟棄,不管有多少輸出,只要不超時(shí)就總會(huì)得到所有字符的,這個(gè)參數(shù)的設(shè)置僅僅影響匹配的行為。

這個(gè)參數(shù)一般在 expect() 命令中設(shè)置, pexpect 2.x 版本似乎有一個(gè) bug ,在 spawn 中設(shè)置是不生效的。

logfile - 運(yùn)行輸出控制

默認(rèn)值: None

當(dāng)給 logfile 參數(shù)指定了一個(gè)文件句柄時(shí),所有從標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出獲得的內(nèi)容都會(huì)寫(xiě)入這個(gè)文件中(注意這個(gè)寫(xiě)入是 copy 方式的),如果指定了文件句柄,那么每次向程序發(fā)送指令(process.send)都會(huì)刷新這個(gè)文件(flush)。

這里有一個(gè)很重要的技巧:如果你想看到spawn過(guò)程中的輸出,那么可以將這些輸出寫(xiě)入到 sys.stdout 里去,比如:

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

用這樣的方式可以看到整個(gè)程序執(zhí)行期間的輸入和輸出,很適合調(diào)試。

還有一個(gè)例子:

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

注意: logfile.txt 文件里,既包含了程序運(yùn)行時(shí)的輸出,也包含了 spawn 向程序發(fā)送的內(nèi)容,有的時(shí)候你也許不希望這樣,因?yàn)槟承﹥?nèi)容出現(xiàn)了2次,那么還有 2 個(gè)很重要的 logfile 關(guān)聯(lián)參數(shù):

logfile_read - 獲取標(biāo)準(zhǔn)輸出的內(nèi)容

默認(rèn)值: None

記錄執(zhí)行程序中返回的所有內(nèi)容,也就是去掉你發(fā)出去的命令,而僅僅只包括命令結(jié)果的部分:

process.logfile_read = sys.stdout

上面的語(yǔ)句會(huì)在屏幕上打印程序執(zhí)行過(guò)程中的所有輸出,但是一般不包含你向程序發(fā)送的命令,不過(guò)大部分程序都有回顯機(jī)制,比如發(fā)命令的時(shí)候設(shè)備不光接收到命令字符串,還會(huì)反向在你的終端上把字符串顯示出來(lái)讓你明白哪些字符被輸入了,這種時(shí)候也是會(huì)被這個(gè)方法讀到的。只有那些不會(huì)回顯的情況 logfile_read 才會(huì)拿不到,比如輸入密碼的時(shí)候。

logfile_send - 獲取發(fā)送的內(nèi)容

默認(rèn)值: None

記錄向執(zhí)行程序發(fā)送的所有內(nèi)容

process.logfile_send = sys.stdout

上面的語(yǔ)句僅僅在屏幕上打印向程序發(fā)送的內(nèi)容。

cwd - 指定命令執(zhí)行的目錄

默認(rèn)值: None 或者說(shuō) ./

cwd 用來(lái)指定命令發(fā)送的命令在哪個(gè)路徑下執(zhí)行,它一般是用在 send() 系列命令中,比如在 Linux 中,你想在 /etc 目錄下執(zhí)行 ls –l 命令,那么完全不需要用 sendline("cd /etc && ls -l") 這樣的方式,而是用 sendline("ls –l", cwd="/etc") 就可以了。

env - 指定環(huán)境變量

默認(rèn)值: None

指定環(huán)境變量的值,這個(gè)值是一個(gè)字典,如果你發(fā)送的命令要使用一些環(huán)境變量,那么可以在這里提供

ignore_sighup - 是否過(guò)濾 SIGHUP 信號(hào)

默認(rèn)值: True

這個(gè)參數(shù)是 pexpect 3.1 開(kāi)始引入的,在 3.1 之前(比如 pexpect 2.3),spawn 的子程序會(huì)過(guò)濾 SIGHUP 信號(hào),也就是用 Ctrl+C 是不能終止子程序的,3.1的默認(rèn)值也繼承了這個(gè)行為,但是如果設(shè)置 ignore_sighup = False 就可以改變這個(gè)行為。

delaybeforesend - 字符發(fā)送延時(shí)

默認(rèn)值: 0.05

這是一個(gè)隱藏參數(shù)用來(lái)設(shè)置發(fā)送字符串之前的延時(shí)。增加這個(gè)參數(shù)的最大理由是因?yàn)楹芏嗳伺鲆?jiàn)這樣一個(gè)問(wèn)題:

在 FTP 程序中登錄時(shí)如果用腳本輸入密碼時(shí)會(huì)直接顯示出來(lái)。這是基于一個(gè)一般人不可思議的事實(shí):當(dāng) FTP 登錄時(shí),實(shí)際上服務(wù)器會(huì)先打印要求你輸入密碼的提示符,然后再發(fā)一個(gè)信號(hào)把回顯功能取消,當(dāng)人使用鍵盤(pán)輸入的時(shí)候因?yàn)檫@個(gè)動(dòng)作延時(shí)比較高所以不可能看到回顯的密碼,但腳本會(huì)在發(fā)現(xiàn)輸入密碼的提示符時(shí)立即發(fā)送,于是密碼就會(huì)在關(guān)閉回顯之前出現(xiàn)了。 Pexpect 為了解決這個(gè)問(wèn)題在每次發(fā)送字符前默認(rèn)等待 50 毫秒,如果你認(rèn)為不必要的話(huà)就可以自己設(shè)置為 0 來(lái)取消這個(gè)行為。

expect() - 關(guān)鍵字匹配

當(dāng) spawn() 啟動(dòng)了一個(gè)程序并返回程序控制句柄后,就可以用 expect() 方法來(lái)等待指定的關(guān)鍵字了。它最后會(huì)返回 0 表示匹配到了所需的關(guān)鍵字,如果后面的匹配關(guān)鍵字是一個(gè)列表的話(huà),就會(huì)返回一個(gè)數(shù)字表示匹配到了列表中第幾個(gè)關(guān)鍵字,從 0 開(kāi)始計(jì)算。

expect() 利用正則表達(dá)式來(lái)匹配所需的關(guān)鍵字。(正則表達(dá)式使用范圍非常廣,幾乎所有語(yǔ)言都對(duì)它提供支持,如果不知道如何使用的話(huà),可以參考我的另一份文檔《正則表達(dá)式參考》)。

它的使用方式:

# pattern_list   正則表達(dá)式列表,表示要匹配這些內(nèi)容
# timeout      不設(shè)置或者設(shè)置為-1的話(huà),超時(shí)時(shí)間就采用self.timeout的值,默認(rèn)是30秒。也可以自己設(shè)置。
# searchwindowsize 功能和 spawn 上的一樣,但是!請(qǐng)注意這個(gè)但是!下面會(huì)實(shí)際說(shuō)明
process.expect(pattern_list, timeout=-1, searchwindowsize=None)

在這里的 searchwindowsize 是在 expect() 方法中真正生效的,默認(rèn)情況下是 None,也就是每從子進(jìn)程中獲取一個(gè)字符就做一次完整匹配,如果子進(jìn)程的輸出很多的話(huà)……性能會(huì)非常低。如果設(shè)置為其他的值,表示從子進(jìn)程中讀取到多少個(gè)字符才做一次匹配,這樣會(huì)顯著減少匹配的次數(shù),增加性能。

經(jīng)過(guò)測(cè)試,對(duì)于一個(gè)有 48100000 個(gè)字符的子進(jìn)程,將 searchwindowsize 設(shè)置為 2000 時(shí),完全處理完成需要 73.2730 秒;同樣的子進(jìn)程將這個(gè)參數(shù)設(shè)置為 None 則需要 1949.6259 秒,Oh, my Lady GAGA…… 完全是指數(shù)上升啊。

最簡(jiǎn)單的匹配方式

process.expect('[Nn]ame')

上面的代碼表示:匹配 process 這個(gè)句柄(代表 spawn 方法的例子中我們啟動(dòng)的 ftp 連接)中的 name 關(guān)鍵字,其中 n 不分大小寫(xiě)。

上面的關(guān)鍵字一旦匹配,就會(huì)返回0表示匹配成功,但是如果一直匹配不到呢?默認(rèn)是會(huì)一直等下去,但是如果設(shè)置了 timeout 的話(huà)就會(huì)超時(shí)。

匹配一系列輸出

實(shí)際上, expect() 可以匹配一系列輸出,通過(guò)檢查匹配到的輸出,我們可以做不同的事情。比如之前 spawn 的 ftp 連接,如果我們輸入用戶(hù)名之后有不同的情況,就可以通過(guò)監(jiān)控這些不同情況來(lái)做不同的動(dòng)作,比如:

index = process.expect([
  'Permission Denied',
  'Terminal type',
  'ftp>',
])
if index == 0:
  print "Permission denied at host, can't login."
  process.kill(0)
elif index == 1:
  print "Login ok, set up terminal type…"
  process.sendline('vty100')
  process.expect("ftp>")
elif index == 2:
  print "Login Ok, please send your command"
  process.interact()

上面的代碼中,expect 方法中的是一個(gè)列表,列表中的每個(gè)元素都是一個(gè)關(guān)鍵字的正則表達(dá)式,也就是說(shuō)我們期待這 3 種情況之一,而 expect 返回一個(gè)順序值來(lái)代表我匹配到了哪一個(gè)元素(也就是發(fā)生了哪種情況了),這個(gè)順序值是從 0 開(kāi)始計(jì)算的。

當(dāng)expect之后,下面的 if 語(yǔ)句就開(kāi)始處理這 3 種情況了:

權(quán)限不足,這可能是 ftp 服務(wù)器出現(xiàn)問(wèn)題,或者沒(méi)有這個(gè)帳號(hào),或者其他什么情況,反正只要發(fā)現(xiàn)這種情況的話(huà),我們就給用戶(hù)提示一下,然后殺掉這個(gè)進(jìn)程

登陸成功,但還要用戶(hù)指定終端模式才能真正使用,所以我們?cè)诖a中指定了 vty100 這種模式,然后看是不是能真正使用了

還是登陸成功了,而且還可以直接輸入命令操作 ftp 服務(wù)器了,于是我們提示用戶(hù),然后把操作權(quán)限交給用戶(hù)

另外有一種特殊情況,如果同時(shí)有2個(gè)被匹配到,那么怎么辦?簡(jiǎn)單來(lái)說(shuō)就是這樣:

原始流中,第一個(gè)被關(guān)鍵字匹配到的內(nèi)容會(huì)被使用

匹配關(guān)鍵字列表中,最左邊的會(huì)被使用

給個(gè)例子:

# 如果流里面的內(nèi)容是 "hello world"
index = process.expect(["hi", "hello", "hello world"])

返回的值是 1,也就是 'hello' 被匹配到了,哪怕真正最好的匹配是 "hello world" 但因?yàn)榉旁诤竺嫠匀匀粺o(wú)效。

使用技巧

如果要檢查或者匹配 expect.EOF 和 expect.TIMEOUT 這兩種情形,那么必須將它們放進(jìn)匹配列表里面去,這樣可以通過(guò)檢查返回的數(shù)字來(lái)處理它們。如果沒(méi)放進(jìn)列表的話(huà),就會(huì)發(fā)生 EOF 或者 TIMEOUT 錯(cuò)誤,程序就會(huì)中途停止了

匹配規(guī)則中有些特殊語(yǔ)法,比如下面的規(guī)則中前 2 個(gè)匹配都是大小寫(xiě)無(wú)關(guān)的,關(guān)鍵就是這個(gè) (?i) 匹配規(guī)則,它相當(dāng)于 re.IGNORE 或者 re.I 這個(gè)關(guān)鍵字,因?yàn)楫吘共皇钦嬲恼齽t表達(dá)式引擎,所以 pexpect 使用這樣特殊語(yǔ)法:

child.expect(['(?i)etc', '(?i)readme', pexpect.EOF, pexpect.TIMEOUT])

expect_exact() - 精確匹配

它的使用和 expect() 是一樣的,唯一不同的就是它的匹配列表中不再使用正則表達(dá)式。

從性能上來(lái)說(shuō) expect_exact() 要更好一些,因?yàn)榧词鼓銢](méi)有使用正則表達(dá)式而只是簡(jiǎn)單的用了幾個(gè)字符 expect() 也會(huì)先將它們轉(zhuǎn)換成正則表達(dá)式模式然后再搜索,但 expect_exact() 不會(huì),而且也不會(huì)把一些特殊符號(hào)轉(zhuǎn)換掉。

expect_list() - 預(yù)轉(zhuǎn)換匹配

使用方式和 expect() 一樣,唯一不同的就是它里面接受的正則表達(dá)式列表只會(huì)轉(zhuǎn)換一次。

expect() 稍微有點(diǎn)笨,每調(diào)用一次它都會(huì)將內(nèi)部的正則表達(dá)式轉(zhuǎn)換一次(當(dāng)然也有其他辦法避免),如果你是在以后循環(huán)中調(diào)用 expect() 的話(huà),多余的轉(zhuǎn)換動(dòng)作就會(huì)降低性能,在這種情況下建議用 expect_list() 來(lái)代替。

使用方法:

# timeout 為 -1 的話(huà)使用 self.timeout 的值
# searchwindowsize 為 -1 的話(huà),也使用系統(tǒng)默認(rèn)的值
process.expect_list(pattern_list, timeout=-1, searchwindowsize=-1)

expect_loop()

用于從標(biāo)準(zhǔn)輸入中獲取內(nèi)容,loop這個(gè)詞代表它會(huì)進(jìn)入一個(gè)循環(huán),必須要從標(biāo)準(zhǔn)輸入中獲取到關(guān)鍵字才會(huì)往下繼續(xù)執(zhí)行。

使用方法:

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

send() - 發(fā)送關(guān)鍵字

send() 作為3個(gè)關(guān)鍵操作之一,用來(lái)向程序發(fā)送指定的字符串,它的使用沒(méi)什么特殊的地方,比如:

process.expect("ftp>")
process.send("by\n")

這個(gè)方法會(huì)返回發(fā)送字符的數(shù)量。

sendline() - 發(fā)送帶回車(chē)符的字符串

sendline() 和 send() 唯一的區(qū)別就是在發(fā)送的字符串后面加上了回車(chē)換行符,這也使它們用在了不同的地方:

只需要發(fā)送字符就可以的話(huà)用send()

如果發(fā)送字符后還要回車(chē)的話(huà),就用 sendline()

它也會(huì)返回發(fā)送的字符數(shù)量

sendcontrol() - 發(fā)送控制信號(hào)

sendcontrol() 向子程序發(fā)送控制字符,比如 <kbd>ctrl+C</kbd> 或者 <kbd>ctrl+D</kbd> 之類(lèi)的,比如你要向子程序發(fā)送 <kbd>ctrl+G</kbd>,那么就這樣寫(xiě):

process.sendcontrol('g')

sendeof() - 發(fā)送 EOF 信號(hào)

向子程序發(fā)送 End Of File 信號(hào)。

sendintr() - 發(fā)送終止信號(hào)

向子程序發(fā)送 SIGINT 信號(hào),相當(dāng)于 Linux 中的 kill 2 ,它會(huì)直接終止掉子進(jìn)程。

interact() - 將控制權(quán)交給用戶(hù)

interact() 表示將控制權(quán)限交給用戶(hù)(或者說(shuō)標(biāo)準(zhǔn)輸入)。一般情況下 pexpect 會(huì)接管所有的輸入和輸出,但有的時(shí)候還是希望用戶(hù)介入,或者僅僅是為了完成一部分工作的時(shí)候, interact() 就很有用了。

比如:

登陸 ftp 服務(wù)器的時(shí)候,在輸入用戶(hù)密碼階段希望用戶(hù)手工輸入密碼,然后腳本完成剩余工作時(shí)(將用戶(hù)密碼寫(xiě)在腳本中可不安全)

只希望完成登陸工作,比如要 ssh 連接到一臺(tái)遠(yuǎn)方的服務(wù)器,但中間要經(jīng)過(guò)好幾跳,用手工輸入實(shí)在太麻煩,所以就用腳本先跳到目的服務(wù)器上,然后再把控制權(quán)限還給用戶(hù)做操作。

使用方法:

# escape_character 就是當(dāng)用戶(hù)輸出這里指定的字符以后表示自己的操作完成了,將控制權(quán)重新交給 pexpect
process.interact(escape_character='\x1d', input_filter=None, output_filter= None)

詳細(xì)來(lái)說(shuō),這個(gè)方法將控制權(quán)交給用戶(hù)(或者說(shuō)用戶(hù)操作的鍵盤(pán)),然后簡(jiǎn)單的將標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯(cuò)誤輸出和標(biāo)準(zhǔn)輸入綁定到系統(tǒng)上來(lái)。

通過(guò)設(shè)置 escape_character 的值,可以定義返回碼,默認(rèn)是 <kbd>ctrl+]</kbd> 或者說(shuō) <kbd>^]</kbd>,當(dāng)輸入了返回碼以后,腳本會(huì)將控制權(quán)從用戶(hù)那里重新拿回來(lái),然后繼續(xù)向下執(zhí)行。

close() - 停止應(yīng)用程序

如果想中途關(guān)閉子程序,那么可以用 close 來(lái)完成,調(diào)用這個(gè)方法后會(huì)返回這個(gè)程序的返回值。

如果設(shè)置 force=True 會(huì)強(qiáng)行關(guān)閉這個(gè)程序,大概的過(guò)程就是先發(fā)送 SIGHUP 和 SIGINT 信號(hào),如果都無(wú)效的話(huà)就發(fā) SIGKILL 信號(hào),反正不管怎么樣都會(huì)保證這個(gè)程序被關(guān)閉掉。

多次調(diào)用這個(gè)方法是允許的,但是不保證每次都能返回正確的返回值。盡量不要這么做,如果想保證程序被關(guān)閉的話(huà)只要設(shè)置force的值就可以了。

下面是實(shí)例:

process.close(force=True)

terminate() - 停止應(yīng)用程序

可以看作是上面 close() 的別名,因?yàn)椴还苁枪δ苓€是使用方法都是一樣的。

Kill() - 發(fā)送 SIGKILL 信號(hào)

向子程序發(fā)送 SIGKILL 的信號(hào)。

flush()

什么都不干,只是為了與文件方法兼容而已。

isalive() - 檢查子程序運(yùn)行狀態(tài)

檢查被調(diào)用的子程序是否正在運(yùn)行,這個(gè)方法是運(yùn)行在非阻斷模式下面的。

如果獲得的返回是 True 表示子程序正在運(yùn)行;返回 False 則表示程序運(yùn)行終止。

isatty() - 檢查是否運(yùn)行在 TTY 設(shè)備上

返回 True 表示打開(kāi)和連接到了一個(gè) tty 類(lèi)型的設(shè)備,或者返回 False 表示未連接。

next() - 返回下一行內(nèi)容

和操作文件一樣,這個(gè)方法也是返回緩存中下一行的內(nèi)容。

read() - 返回剩下的所有內(nèi)容

獲取子程序返回的所有內(nèi)容,一般情況下我們可以用 expect 來(lái)期待某些內(nèi)容,然后通過(guò) process.before 這樣的方式來(lái)獲取,但這種方式有一個(gè)前提:那就是必須先 expect 某些字符,然后才能用 process.before 來(lái)獲取緩存中剩下的內(nèi)容。

read() 的使用很不同,它期待一個(gè) EOF 信號(hào),然后將直到這個(gè)信號(hào)之前的所有輸出全部返回,就像讀一個(gè)文件那樣。

一般情況下,交互式程序只有關(guān)閉的時(shí)候才會(huì)返回 EOF ,比如用 by 命令關(guān)閉 ftp 服務(wù)器,或者用 exit 命令關(guān)閉一個(gè) ssh 連接。

這個(gè)方法使用范圍比較狹窄,因?yàn)橥耆梢杂?expect.EOF 方式來(lái)代替。當(dāng)然如果是本機(jī)命令,每執(zhí)行完一次之后都會(huì)返回 EOF ,這種情況下倒是很有用:

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

看起來(lái)這么做有點(diǎn)無(wú)聊?但我想一定有什么理由支持這個(gè)方法。

可以用指定 read(size=-1) 的方式來(lái)設(shè)置返回的字符數(shù),如果沒(méi)有設(shè)置或者設(shè)置為負(fù)數(shù)則返回所有內(nèi)容,正數(shù)則返回指定數(shù)量的內(nèi)容,返回的內(nèi)容是字符串形式。

readline() - 返回一行輸出

返回一行輸出,返回的內(nèi)容包括最后的\r\n字符。

也可以設(shè)置 readline(size=-1) 來(lái)指定返回的字符數(shù),默認(rèn)是負(fù)數(shù)表示返回所有的。

readlines() - 返回列表模式的所有輸出

返回一個(gè)列表,列表中的每個(gè)元素都是一行(包括\r\n字符)。

setecho() - 子程序響應(yīng)模式

設(shè)置子程序運(yùn)行時(shí)的響應(yīng)方式,一般情況下向子程序發(fā)送字符的時(shí)候,這些字符都會(huì)在標(biāo)準(zhǔn)輸出上顯示出來(lái),這樣你可以看到你發(fā)送出去的內(nèi)容,但是有的時(shí)候,我們不需要顯示,那么就可以用這個(gè)方法來(lái)設(shè)置了。

注意,必須在發(fā)送字符之前設(shè)置,設(shè)置之后在之后的代碼中都一直有效。比如:

process = pexpect.spawn('cat')
 
# 默認(rèn)情況下,下面的1234這個(gè)字符串會(huì)顯示2次,一次是pexpect返回的,一次是cat命令返回的
process.sendline("1234")
 
# 現(xiàn)在我們關(guān)閉pexpect()的echo功能
process.setecho(False)
 
# 下面的字符只會(huì)顯示一次了,這是由cat返回的
process.sendline("abcd")
 
# 現(xiàn)在重新開(kāi)啟echo功能,就可以再次看到我們發(fā)送的字符了
process.setecho(True)

setwinsize() - 控制臺(tái)窗口大小

如果子程序是一個(gè)控制臺(tái)(TTY),比如 SSH 連接、 Telnet 連接這種通過(guò)網(wǎng)絡(luò)登陸到系統(tǒng)并發(fā)送命令的都算控制臺(tái),那么可以用這個(gè)方法來(lái)設(shè)置這個(gè)控制太的大?。ɑ蛘哒f(shuō)長(zhǎng)寬)。

它的調(diào)用方式是 process.setwinsize(r, c)

默認(rèn)值是 setwinsize(24, 80),其中 24 是高度,單位是行; 80 是寬度,單位是字符。

為什么要用它?想像下面的場(chǎng)景:

有的時(shí)候你通過(guò)pexpect登陸到某個(gè)ssh控制臺(tái)之后,又用 interact() 來(lái)將控制權(quán)交給用戶(hù),然后用戶(hù)到控制臺(tái)里面寫(xiě)自己的命令,如果命令比較長(zhǎng),就會(huì)發(fā)現(xiàn)當(dāng)命令到屏幕邊緣之后不會(huì)自動(dòng)換行,而是又返回到這一行的最前面重新覆蓋前面的字符;這不會(huì)影響命令的實(shí)際效果,但是很惱人。

這種情況用 setwinsize() 就可以解決,找到自己終端支持的長(zhǎng)度,重新設(shè)置一下,比如 setwinsize(25, 96 ),如果設(shè)置的正確的話(huà)就可以解決了。

wait() - 執(zhí)行等待

直到被調(diào)用的子程序執(zhí)行完畢之前,程序都停止(或者說(shuō)等待)執(zhí)行。它不會(huì)從被調(diào)用的子程序中讀取任何內(nèi)容。

waitnoecho()

它使用的地方比較特殊,唯一匹配的地方就是:當(dāng)子程序的 echo 功能被設(shè)置為 Fals 時(shí)。

看起來(lái)很奇怪?其實(shí)這個(gè)功能是基于一個(gè)很讓人難以置信但的確是真實(shí)的情況:

在命令行模式下,很多要求輸入密碼的地方,比如 FTP/SSH 等,密碼實(shí)際上都會(huì)在你輸入之后又重新返回并打印出來(lái)的,但是為什么我們看不到我們自己輸入的密碼呢?這就是因?yàn)槊艽a在要打印出來(lái)之前被程序?qū)?echo 功能設(shè)置為 False 了。

現(xiàn)在知道為什么有這么一個(gè)方法了吧?比如要進(jìn)行一個(gè) ssh 連接時(shí),如何檢查是否要輸入密碼?用關(guān)鍵字 password 是一個(gè)方法,但還有一個(gè)方法就是這樣:

# 啟動(dòng)ssh連接
process = pexpect.spawn("ssh user@example.com")
 
# 等待echo被設(shè)置為False,這就意味著本地不會(huì)有回顯
process.waitnoecho()
process.sendline('mypassword')

可以設(shè)置超時(shí)時(shí)間,默認(rèn)是:waitnoecho(timeout=-1),表示和系統(tǒng)設(shè)置的超時(shí)時(shí)間相同,也可以設(shè)置為 None 表示永遠(yuǎn)等待,直到回顯被設(shè)置為 False ,當(dāng)然還可以設(shè)置其他的數(shù)字來(lái)表示超時(shí)時(shí)間。

write() - 發(fā)送字符串

類(lèi)似于send()命令,只不過(guò)不會(huì)返回發(fā)送的字符數(shù)。

writelines() - 發(fā)送包含字符串的列表

類(lèi)似于 write() 命令,只不過(guò)接受的是一個(gè)字符串列表, writelines() 會(huì)向子程序一條一條的發(fā)送列表中的元素,但是不會(huì)自動(dòng)在每個(gè)元素的最后加上回車(chē)換行符。

與 write() 相似的是,這個(gè)方法也不會(huì)返回發(fā)送的字符數(shù)量。

特殊變量

pexpect.EOF - 匹配終止信號(hào)

EOF 變量使用范圍很廣泛,比如檢查 ssh/ftp/telnet 連接是否終止啊,文件是否已經(jīng)到達(dá)末尾啊。 pexpect 大部分腳本的最后都會(huì)檢查 EOF 變量來(lái)判斷是不是正常終止和退出,比如下面的代碼:

process.expect("ftp>")
process.sendline("by")
process.expect(pexpect.EOF)
print "ftp connect terminated."

pexpect.TIMEOUT - 匹配超時(shí)信號(hào)

TIMEOUT 變量用來(lái)匹配超時(shí)的情況,默認(rèn)情況下 expect 的超時(shí)時(shí)間是 60 秒,如果超過(guò) 60 秒還沒(méi)有發(fā)現(xiàn)期待的關(guān)鍵字,就會(huì)觸發(fā)這個(gè)行為,比如:

# 匹配pexpect.TIMEOUT的動(dòng)作,只有超時(shí)事件發(fā)生的時(shí)候才會(huì)有效
index = process.expect(['ftp>', pexpect.TIMEOUT],)
if index == 1:
  process.interactive()  ; # 將控制權(quán)交給用戶(hù)
elif index == 2:
  print "Time is out."
  process.kill(0)     ; # 殺掉進(jìn)程
 
 
 
# 那么怎么改變超時(shí)時(shí)間呢?其實(shí)可以修改spawn對(duì)象里的timeout參數(shù):
# 下面的例子僅僅加了一行,這樣就改變了超時(shí)的時(shí)間了
process.timeout = 300  ; # 注意這一行
index = process.expect(['ftp>', pexpect.TIMEOUT],)
if index == 1:
  process.interactive()  ; # 將控制權(quán)交給用戶(hù)
elif index == 2:
  print "Time is out."
  process.kill(0)     ; # 殺掉進(jìn)程

process.before/after/match - 獲取程序運(yùn)行輸出

當(dāng) expect() 過(guò)程匹配到關(guān)鍵字(或者說(shuō)正則表達(dá)式)之后,系統(tǒng)會(huì)自動(dòng)給3個(gè)變量賦值,分別是 before, after 和 match

process.before - 保存了到匹配到關(guān)鍵字為止,緩存里面已有的所有數(shù)據(jù)。也就是說(shuō)如果緩存里緩存了 100 個(gè)字符的時(shí)候終于匹配到了關(guān)鍵字,那么 before 就是除了匹配到的關(guān)鍵字之外的所有字符

process.after - 保存匹配到的關(guān)鍵字,比如你在 expect 里面使用了正則表達(dá)式,那么表達(dá)式匹配到的所有字符都在 after 里面

process.match - 保存的是匹配到的正則表達(dá)式的實(shí)例,和上面的 after 相比一個(gè)是匹配到的字符串,一個(gè)是匹配到的正則表達(dá)式實(shí)例

如果 expect() 過(guò)程中發(fā)生錯(cuò)誤,那么 before 保存到目前位置緩存里的所有數(shù)據(jù), after 和 match 都是 None

self.exitstatus | self.signalstatus

上面的2個(gè)值用來(lái)保存spawn子程序的退出狀態(tài),但是注意:只有使用了 process.close() 命令之后這 2 個(gè)參數(shù)才會(huì)被設(shè)置。

其他說(shuō)明

CR/LF約定

眾所周知的是:世界上有很多種回車(chē)換行約定,它們給我們?cè)斐闪撕芏嗦闊热纾?/p>

windows 中用 \r\n 表示回車(chē)換行

Linux like 系統(tǒng)中用 \r 表示回車(chē)換行

Mac 系統(tǒng)中用 \n 表示回車(chē)換行

這種種回車(chē)換行約定對(duì)代碼移植造成了很大的困難,幾乎所有全平臺(tái)支持的程序語(yǔ)言都有它們自己的解決方案,而 pexpect 的解決方案就是:

不管哪個(gè)平臺(tái),回車(chē)換行都替換成 \r\n

所以,如果我們要在expect中匹配回車(chē)換行符號(hào)的話(huà),就必須這么做:

process.expect('\r\n')
 
# 想匹配一行里的最后一個(gè)單詞:
process.expect('\w+\r\n')
 
# 下面的匹配方式是錯(cuò)誤的(在其他腳本語(yǔ)言中是正確的,比如TCL語(yǔ)言的Expect實(shí)現(xiàn)中,這也是很容易搞混淆的地方):
process.expect('\r')

$ * + 約定

正則表達(dá)式中, $ 符號(hào)表示從一行中的最后開(kāi)始匹配——但是在 pexpect 中是無(wú)效的。如果要匹配一行的最后,那么必須有一行數(shù)據(jù)存在,也就是有回車(chē)換行符,但是 pexpect 的處理不是按行來(lái)進(jìn)行的,它一次僅僅讀一個(gè)并且處理一個(gè)字符,而且不會(huì)處理【未來(lái)】的數(shù)據(jù)。

所以不管什么時(shí)候,都不要在 expect() 中用 $ 符號(hào)來(lái)匹配。

正因?yàn)閜expect一次僅僅處理一個(gè)字符,所以加號(hào) (+) 、星號(hào) (*) 的功能也無(wú)效了,比如:

# 無(wú)論何時(shí),都只會(huì)返回一個(gè)字符
process.expect(".+")
 
# 無(wú)論何時(shí),都只會(huì)返回空字符
process.expect(".*")

程序調(diào)試

如果要調(diào)試pexpect,那么可以使用下面的方式:

str(processHandle)
 
# 通過(guò) pexpect.spawn() 可以創(chuàng)建一個(gè)進(jìn)程,并通過(guò)操作這個(gè)進(jìn)程的句柄來(lái)控制程序。
# 但是如果將這個(gè)句柄用 str() 函數(shù)重載一下呢?它會(huì)顯示這個(gè)控制句柄的一系列內(nèi)部信息,比如:
process = pexpect.spawn("ftp sw-tftp")
print str(process)
# <pexpect.spawn object at 0x2841cacc>
version: 2.3 ($Revision: 399 $)
command: /usr/bin/ftp
args: ['/usr/bin/ftp', 'sw-tftp']
searcher: searcher_re:
  0: EOF
buffer (last 100 chars):
before (last 100 chars): was 14494 bytes in 1 transfers.
221-Thank you for using the FTP service on sw-tftp.
221 Goodbye.
 
after: <class 'pexpect.EOF'>
match: <class 'pexpect.EOF'>
match_index: 0
exitstatus: 0
flag_eof: True
pid: 50733
child_fd: 3
closed: False
timeout: 30
delimiter: <class 'pexpect.EOF'>
logfile: None
logfile_read: None
logfile_send: None
maxread: 2000
ignorecase: False
searchwindowsize: None
delaybeforesend: 0.05
delayafterclose: 0.1
delayafterterminate: 0.1

技巧和陷阱

循環(huán)匹配

Python 的 pexpect 模塊與 TCL 的 expect 相比有些功能明顯支持不足,其中就包括循環(huán)匹配。 TCL 的 expect 模塊可以給出一系列匹配關(guān)鍵字,然后通過(guò) continue 語(yǔ)句的設(shè)置保證同一個(gè) expect 可以在關(guān)鍵字列表中重新循環(huán)。

比如一個(gè) expect 有 3 個(gè)關(guān)鍵字,其中匹配到第二個(gè)關(guān)鍵字的時(shí)候會(huì)碰見(jiàn) continue 語(yǔ)句,那么下一次匹配就重復(fù)這個(gè) expect 過(guò)程,這是一個(gè)很有用的功能,比如超時(shí)時(shí)間設(shè)置為 10 秒,然后重復(fù) 3 次才會(huì)真正超時(shí)的情況。

可惜的是 Python 的 pexpect 沒(méi)有這樣的功能。但是想模擬這種情況也不是不可以,可以通過(guò) while 語(yǔ)句來(lái)完成,比如:

while True:
  index = process.expect([
    pexpect.TIMEOUT,
    pexpect.EOF,
  ]}
  if index == 0:
    print "time is out"
    # 重新從開(kāi)始匹配
    continue
elif index == 1:
  print "Terminate."
  # 終止循環(huán)
  break

獲取 before 中內(nèi)容的戰(zhàn)略與清空 buffer

絕大多數(shù)情況下我們都會(huì)利用before變量來(lái)獲取命令執(zhí)行的結(jié)果。但是,你真的知道怎么用好 before 么?

before 中到底什么時(shí)候保存你所需的內(nèi)容?這個(gè)細(xì)節(jié)必須非常清楚,我們以一個(gè)調(diào)用一個(gè)命令為例子:

這里我們預(yù)計(jì)是在 linux 系統(tǒng)中,下面的 handle 是一個(gè) spawn 后的句柄,而 prompt 則是 bash 的提示符。我們預(yù)計(jì)做這樣的步驟:

匹配提示符,以此判斷系統(tǒng)已經(jīng)準(zhǔn)備好接受命令

發(fā)送命令

獲取命令執(zhí)行后的結(jié)果

handle.expect(prompt)
handle.sendline("ls –l")
handle.expect(prompt)
output = handle.before

一共 4 個(gè)語(yǔ)句,就可以獲取 ls –l 命令的結(jié)果了,但是且慢,是否發(fā)現(xiàn)有什么不合理的地方?

第一句和第二句分別是匹配系統(tǒng)提示符和發(fā)送命令,這都是比較正常的。

但是為什么第三句是再次匹配系統(tǒng)提示符?在一般的想像下,發(fā)送命令之后,設(shè)備就會(huì)執(zhí)行并返回結(jié)果了,那么完全就可以用 handle.before 語(yǔ)句來(lái)獲取到這些內(nèi)容了才對(duì)???

實(shí)際上,從 before 這個(gè)單詞就可以大概明白,它并不是實(shí)時(shí)生效的,它里面的內(nèi)容,實(shí)際上是上一次 expect 匹配之后,除掉匹配到的關(guān)鍵字本身,系統(tǒng)緩存中剩余下來(lái)的全部?jī)?nèi)容。也就是說(shuō),如果第三句就是 output = handle.before的話(huà),那么它里面的內(nèi)容就是第一句的那個(gè) expect 中去掉 prompt 內(nèi)容后緩存中剩下來(lái)的內(nèi)容。顯然,這里面不會(huì)包括后面 ls –l 命令的內(nèi)容了。

那么想獲取 ls –l 的內(nèi)容,唯一的辦法是再增加一個(gè) expect 關(guān)鍵字匹配。這是非常關(guān)鍵的一點(diǎn)。

另外, pexpect 中的 buffer 是一個(gè)關(guān)鍵,但又不能被直接操作的變量,它保存的是運(yùn)行過(guò)程中每一個(gè) expect 之后的所有內(nèi)容,隨時(shí)被更新。而 before/after 都是直接源于它的,而 expect 的關(guān)鍵字匹配本身也是在 buffer 中做匹配的。

正因?yàn)樗闹匾裕瑢?duì)這個(gè)變量中的內(nèi)容需要特別的警惕。比如我們將登陸設(shè)備,發(fā)送命令,退出設(shè)備這3個(gè)步驟寫(xiě)進(jìn)3個(gè)函數(shù)的時(shí)候,最好保證每個(gè)步驟都不會(huì)影響下一個(gè)步驟,在每個(gè)步驟開(kāi)始的時(shí)候,最好做這樣的操作:

handle.buffer = ""

代碼實(shí)例

FTP服務(wù)器的登陸

下面的代碼比較簡(jiǎn)單,就是登陸到一個(gè) FTP 服務(wù)器,并自動(dòng)輸入密碼,等進(jìn)入服務(wù)器以后,先輸入幾個(gè)預(yù)定義的命令,然后將控制權(quán)交還給用戶(hù),用戶(hù)操作完成后按 <kbd>ctrl+]</kbd> 表示自己操作完成了,腳本再自動(dòng)退出 ftp 登陸。

#!/usr/bin/env python
 
import sys
import pexpect
 
# FTP服務(wù)器的標(biāo)準(zhǔn)提示符
ftpPrompt = 'ftp>'
 
# 啟動(dòng)FTP服務(wù)器,并將運(yùn)行期間的輸出都放到標(biāo)準(zhǔn)輸出中
process = pexpect.spawn('ftp sw-tftp')
process.logfile_read = sys.stdout
 
# 服務(wù)器登陸過(guò)程
process.expect('[Nn]ame')
process.sendline('dev')
process.expect('[Pp]assword')
process.sendline('abcd1234')
 
# 先自動(dòng)輸入一些預(yù)定命令
cmdList = ("passive", 'hash')
 
for cmd in cmdList:
  process.expect(ftpPrompt)
  process.sendline(cmd)
 
process.expect(ftpPrompt)
 
# 在這里將FTP控制權(quán)交還給用戶(hù),用戶(hù)輸入完成后按 ctrl+] 再將控制權(quán)還給腳本
# ctrl+] 交還控制權(quán)給腳本是默認(rèn)值,用戶(hù)還可以設(shè)置其他的值,比如 ‘\x2a'
# 就是用戶(hù)按星號(hào)的時(shí)候交還。這個(gè)值實(shí)際上是 ASCII 的16進(jìn)制碼,它們的對(duì)應(yīng)關(guān)系
# 可以自己去其他地方找一下,但是注意必須是16進(jìn)制的,并且前綴是 \x
process.interact()
 
# 當(dāng)用戶(hù)將控制權(quán)交還給腳本后,再由腳本退出ftp服務(wù)器
# 注意下面這個(gè)空的sendline()命令,它很重要。用戶(hù)將控制權(quán)交還給腳本的時(shí)候,
# 腳本緩存里面是沒(méi)任何內(nèi)容的,所以也不可能匹配,這里發(fā)送一個(gè)回車(chē)符會(huì)從服務(wù)器取得
# 一些內(nèi)容,這樣就可以匹配了。
# 最后的EOF是確認(rèn)FTP連接完成的方法。
process.sendline()
process.expect(ftpPrompt)
process.sendline('by')
process.expect(pexpect.EOF)

上面的腳本實(shí)際上缺少很多錯(cuò)誤處理,比如登陸以后用戶(hù)名或者密碼錯(cuò)誤,或者無(wú)法連接服務(wù)器之類(lèi)的,但是核心動(dòng)作已經(jīng)完整了。

以上這篇對(duì)Python Pexpect 模塊的使用說(shuō)明詳解就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論