Python模塊--Pexpect

探索 Pexpect,第 1 部分:剖析 Pexpect


概述html

Pexpect 是 Don Libes 的 Expect 語言的一個 Python 實現,是一個用來啓動子程序,並使用正則表達式對程序輸出作出特定響應,以此實現與其自動交互的 Python 模塊。 Pexpect 的使用範圍很廣,能夠用來實現與 ssh、ftp 、telnet 等程序的自動交互;能夠用來自動複製軟件安裝包並在不一樣機器自動安裝;還能夠用來實現軟件測試中與命令行交互的自動化。java

下載

Pexpect 能夠從 SourceForge 網站下載。 本文介紹的示例使用的是 2.3 版本,如不說明測試環境,默認運行操做系統爲 fedora 9 並使用 Python 2.5 。python

安裝

1
2
3
4
download pexpect-2.3.tar.gz
tar zxvf pexpect-2.3.tar.g
cd pexpect-2.3
python setup.py install  (do this as root)

依賴

  • Python 版本 2.4 或者 2.5
  • pty module ,pty 是任何 Posix 系統標準庫的一部分

因爲其依賴 pty module ,因此 Pexpect 還不能在 Windows 的標準 python 環境中執行,若是想在 Windows 平臺使用,可使用在 Windows 中運行 Cygwin 作爲替代方案。linux

遵循 MIT 許可證

根據 Wiki 對 MIT License 的介紹「該模塊被受權人有權利使用、複製、修改、合併、出版發行、散佈、再受權及販售軟件及軟件的副本。被受權人可根據程序的須要修改受權條款爲適當的內容。在軟件和軟件的全部副本中都必須包含版權聲明和許可聲明。」正則表達式

Pexpect 提供的 run() 函數:

清單 1. run() 的定義
1
2
run(command,timeout=-1,withexitstatus=False,events=None,\
     extra_args=None,logfile=None, cwd=None, env=None)

函數 run 能夠用來運行命令,其做用與 Python os 模塊中 system() 函數類似。run() 是經過 Pexpect 類實現的。shell

若是命令的路徑沒有徹底給出,則 run 會使用 which 命令嘗試搜索命令的路徑 。apache

清單 2. 使用 run()執行 svn 命令
1
2
from pexpect import *
run ("svn ci -m 'automatic commit' my_file.py")

與 os.system() 不一樣的是,使用 run() 能夠方便地同時得到命令的輸出結果與命令的退出狀態 。windows

清單 3. run() 的返回值
1
2
from pexpect import *
(command_output, exitstatus) = run ('ls -l /bin', withexitstatus=1)

command_out 中保存的就是 /bin 目錄下的內容。bash

Pexpect 提供的 spawn() 類:

使用 Pexpect 啓動子程序

清單 4. spawn 的構造函數
1
2
3
class spawn:
     def __init__(self,command,args=[],timeout=30,maxread=2000,\
     searchwindowsize=None, logfile=None, cwd=None, env=None)

spawn是Pexpect模塊主要的類,用以實現啓動子程序,它有豐富的方法與子程序交互從而實現用戶對子程序的控制。它主要使用 pty.fork() 生成子進程,並調用 exec() 系列函數執行 command 參數的內容。服務器

能夠這樣使用:

清單 5. spawn() 使用示例
1
2
3
child = pexpect.spawn ('/usr/bin/ftp') #執行ftp客戶端命令
child = pexpect.spawn ('/usr/bin/ssh user@example.com') #使用ssh登陸目標機器
child = pexpect.spawn ('ls -latr /tmp') #顯示 /tmp目錄內容

當子程序須要參數時,還可使用一個參數的列表:

清單 6. 參數列表示例
1
2
3
child = pexpect.spawn ('/usr/bin/ftp', [])
child = pexpect.spawn ('/usr/bin/ssh', ['user@example.com'])
child = pexpect.spawn ('ls', ['-latr', '/tmp'])

在構造函數中,maxread 屬性指定了 Pexpect 對象試圖從 tty 一次讀取的最大字節數,它的默認值是2000字節 。

因爲須要實現不斷匹配子程序輸出, searchwindowsize 指定了從輸入緩衝區中進行模式匹配的位置,默認從開始匹配。

logfile 參數指定了 Pexpect 產生的日誌的記錄位置。

例如:

清單 7. 記錄日誌
1
2
3
child = pexpect.spawn('some_command')
fout = file('mylog.txt','w')
child.logfile = fout

還能夠將日誌指向標準輸出:

清單 8. 將日誌指向標準輸出
1
2
child = pexpect.spawn('some_command')
child.logfile = sys.stdout

若是不須要記錄向子程序輸入的日誌,只記錄子程序的輸出,可使用:

清單 9. 記錄輸出日誌
1
2
child = pexpect.spawn('some_command')
child.logfile_send = sys.stdout

使用 Pexpect 控制子程序

爲了控制子程序,等待子程序產生特定輸出,作出特定的響應,可使用 expect 方法。

清單 10. expect() 定義
1
expect(self, pattern, timeout=-1, searchwindowsize=None)

在參數中: pattern 能夠是正則表達式, pexpect.EOF , pexpect.TIMEOUT ,或者由這些元素組成的列表。須要注意的是,當 pattern 的類型是一個列表時,且子程序輸出結果中不止一個被匹配成功,則匹配返回的結果是緩衝區中最早出現的那個元素,或者是列表中最左邊的元素。使用 timeout 能夠指定等待結果的超時時間 ,該時間以秒爲單位。當超過預訂時間時, expect 匹配到pexpect.TIMEOUT。

若是難以估算程序運行的時間,可使用循環使其屢次等待直至等待運行結束:

清單 11. 使用循環
1
2
3
4
5
6
7
8
while True:
     index = child.expect(["suc","fail",pexpect.TIMEOUT])
     if index == 0:
         break
elif index == 1:
         return False
elif index == 2:
         pass        #continue to wait

expect() 在執行中可能會拋出兩種類型的異常分別是 EOF and TIMEOUF,其中 EOF 一般表明子程序的退出, TIMEOUT 表明在等待目標正則表達式中出現了超時。

清單 12. 使用並捕獲異常
1
2
3
4
5
6
7
8
9
10
try:
     index = pexpect (['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等待的目標列表中:

清單 13. 避免異常
1
2
3
4
5
6
7
8
9
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()

expect 不斷從讀入緩衝區中匹配目標正則表達式,當匹配結束時 pexpect 的 before 成員中保存了緩衝區中匹配成功處以前的內容, pexpect 的 after 成員保存的是緩衝區中與目標正則表達式相匹配的內容。

清單 14. 打印 before 成員的內容
1
2
3
child = pexpect.spawn('/bin/ls /')
child.expect (pexpect.EOF)
print child.before

此時 child.before 保存的就是在根目錄下執行 ls 命令的結果。

清單 15. send 系列函數
1
2
3
send(self, s)
sendline(self, s='')
sendcontrol(self, char)

這些方法用來向子程序發送命令,模擬輸入命令的行爲。 與 send() 不一樣的是 sendline() 會額外輸入一個回車符 ,更加適合用來模擬對子程序進行輸入命令的操做。 當須要模擬發送 「Ctrl+c」 的行爲時,還可使用 sendcontrol() 發送控制字符。

清單 16. 發送 ctrl+c
1
child.sendcontrol('c')

因爲 send() 系列函數向子程序發送的命令會在終端顯示,因此也會在子程序的輸入緩衝區中出現,所以不建議使用 expect 匹配最近一次 sendline() 中包含的字符。不然可能會在形成不但願的匹配結果。

清單 17. interact() 定義
1
interact(self, escape_character = chr(29), input_filter = None, output_filter = None)

Pexpect還能夠調用interact() 讓出控制權,用戶能夠繼續當前的會話控制子程序。用戶能夠敲入特定的退出字符跳出,其默認值爲「^]」 。

下面展現一個使用Pexpect和ftp交互的實例。

清單 18. ftp 交互的實例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# This connects to the openbsd ftp site and
# downloads the README file.
import pexpect
child = pexpect.spawn ('ftp ftp.openbsd.org')
child.expect ('Name .*: ')
child.sendline ('anonymous')
child.expect ('Password:')
child.sendline ('noah@example.com')
child.expect ('ftp> ')
child.sendline ('cd pub/OpenBSD')
child.expect('ftp> ')
child.sendline ('get README')
child.expect('ftp> ')
child.sendline ('bye')

該程序與 ftp 作交互,登陸到 ftp.openbsd.org ,當提述輸入登陸名稱和密碼時輸入默認用戶名和密碼,當出現 「ftp>」 這一提示符時切換到 pub/OpenBSD 目錄並下載 README 這一文件。

如下實例是上述方法的綜合應用,用來創建一個到遠程服務器的 telnet 鏈接,並返回保存該鏈接的 pexpect 對象。

清單 19. 登陸函數:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import re,sys,os
from pexpect import *
 
def telnet_login(server,user, passwd,shell_prompt= 「#|->」): 
     """ 
     @summary: This logs the user into the given server.
     It uses the 'shell_prompt' to try to find the prompt right after login.
     When it finds the prompt it immediately tries to reset the prompt to '#UNIQUEPROMPT#'
     more easily matched. 
     @return: If Login successfully ,It will return a pexpect object   
     @raise exception: RuntimeError will be raised when the cmd telnet failed or the user
     and passwd do not match 
     @attention:1. shell_prompt should not include '$',on some server, after sendline
                   (passwd)   the pexpect object will read a '$'. 
     2.sometimes the server's output before its shell prompt will contain '#' or
      '->'  So the caller should kindly assign the shell prompt 
     """ 
     if not server or not user \ 
           or not passwd or not shell_prompt: 
         raise RuntimeError, "You entered empty parameter for telnet_login " 
       
     child = pexpect.spawn('telnet %s' % server) 
     child.logfile_read = sys.stdout 
     index = child.expect (['(?i)login:', '(?i)username', '(?i)Unknown host']) 
     if index == 2: 
         raise RuntimeError, 'unknown machine_name' + server 
     child.sendline (user) 
     child.expect ('(?i)password:') 
     child.logfile_read = None  # To turn off log
     child.sendline (passwd) 
       
     while True: 
         index = child.expect([pexpect.TIMEOUT,shell_prompt]) 
         child.logfile_read = sys.stdout 
         if index == 0: 
             if re.search('an invalid login', child.before): 
                 raise RuntimeError, 'You entered an invalid login name or password.'
         elif index == 1: 
             break     
     child.logfile_read = sys.stdout # To tun on log again
     child.sendline(「PS1=#UNIQUEPROMPT#」) 
     #This is very crucial to wait for PS1 has been modified successfully 
     #child.expect(「#UNIQUEPROMPT#」) 
     child.expect("%s.+%s" % (「#UNIQUEPROMPT#」, 「#UNIQUEPROMPT#」)) 
     return child

Pxssh 類的使用:

Pxssh 作爲 pexpect 的派生類能夠用來創建一個 ssh 鏈接,它相比其基類增長了以下方法:

login() 創建到目標機器的ssh鏈接 ;

losuckgout() 釋放該鏈接 ;

prompt() 等待提示符,一般用於等待命令執行結束。

下面的示例鏈接到一個遠程服務器,執行命令並打印命令執行結果。

該程序首先接受用戶輸入用戶名和密碼,login 函數返回一個 pxssh 對象的連接,而後調用 sendline() 分別輸入 「uptime」、「ls」 等命令並打印命令輸出結果。

清單 20. pxssh 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import pxssh 
import getpass 
try:
s = pxssh.pxssh()
     hostname = raw_input('hostname: ') 
     username = raw_input('username: ') 
     password = getpass.getpass('password: ') 
     s.login (hostname, username, password) 
     s.sendline ('uptime')  # run a command 
     s.prompt()             # match the prompt 
     print s.before         # print everything before the propt. 
     s.sendline ('ls -l') 
     s.prompt()
     print s.before 
     s.sendline ('df') 
     s.prompt()
     print s.before 
     s.logout()
except pxssh.ExceptionPxssh, e: 
     print "pxssh failed on login."
     print str(e)

Pexpect 使用中須要注意的問題:

spawn() 參數的限制

在使用spawn執行命令時應該注意,Pexpect 並不與 shell 的元字符例如重定向符號 > 、>> 、管道 | ,還有通配符 * 等作交互,因此當想運行一個帶有管道的命令時必須另外啓動一個 shell ,爲了使代碼清晰,如下示例使用了參數列表例如:

清單 21. 啓動新的 shell 執行命令
1
2
3
shell_cmd = 'ls -l | grep LOG > log_list.txt'
child = pexpect.spawn('/bin/bash', ['-c', shell_cmd])
child.expect(pexpect.EOF)

與線程共同工做

Perl 也有 expect 的模塊 Expect-1.21,可是 perl 的該模塊在某些操做系統例如 fedora 9 或者 AIX 5 中不支持在線程中啓動程序執行如下實例試圖利用多線同時程登陸到兩臺機器進行操做,不使用線程直接調用時 sub1() 函數能夠正常工做,可是使用線程時在 fedora9 和 AIX 5 中都不能正常運行。

清單 22. perl 使用 expect 因爲線程和 expect 共同使用致使不能正常工做的程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use threads;
use Expect; 
$timeout = 5; 
my $thr = threads->create(\&sub1(first_server)); 
my $thr2 = threads->create(\&sub1(second_server)); 
sub sub1 
         my $exp = new Expect; 
         $exp -> raw_pty(1); 
         $exp -> spawn ("telnet",$_[0]) or die "cannot access telnet";
         $exp -> expect ( $timeout, -re=>'[Ll]ogin:' ); 
         $exp -> send ( "user\n"); 
         $exp -> expect ( $timeout, -re=>'[Pp]assword:' ); 
         $exp -> send ( "password\n" ); 
         $exp -> expect ( $timeout, -re=>" #" ); 
         $exp -> send ( "date\n" );
         $exp -> expect ( $timeout, -re=>'\w\w\w \w\w\w \d{1,2}
                            \d\d:\d\d:\d\d \w\w\w \d\d\d\d'); 
         $localtime=$exp->match();
         print "\tThe first server’s time is : $localtime\n"; 
         $exp -> soft_close ();
print "This is the main thread!"; 
$thr->join();
$thr2->join();

Pexpect 則沒有這樣的問題,可使用多線程並在線程中啓動程序運行。可是在某些操做系統如 fedora9 中不能夠在線程之間傳遞 Pexpect 對象。

清單 使用 Pexpect 在線程中啓動控制子程序

請參見實例

對正則表達式的支持

在使用 expect() 時,因爲 Pexpect 是不斷從緩衝區中匹配,若是想匹配行尾不能使用 「$」 ,只能使用 「\r\n」表明一行的結束。 另外其只能獲得最小匹配的結果,而不是進行貪婪匹配,例如 child.expect ('.+') 只能匹配到一個字符。

應用實例:

在實際系統管理員的任務中,有時須要同時管理多臺機器,這個示例程序被用來自動編譯並安裝新的內核版本,並重啓。它使用多線程,每一個線程都創建一個到遠程機器的 telnet 鏈接並執行相關命令。 該示例會使用上文中的登陸函數

清單 23. 管理多臺機器示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import sys,os 
from Login import *
PROMPT = 「#UNIQUEPROMPT#」
class RefreshKernelThreadClass(threading.Thread): 
""" The thread to downLoad the kernel and install it on a new server  """
     def __init__(self,server_name,user,passwd): 
         threading.Thread.__init__(self) 
         self.server_name_ = server_name 
         self.user_ = user 
         self.passwd_ = passwd 
         self.result_ = [] # the result information of the thread
          
     def run(self): 
         self.setName(self.server_name_) # set the name of thread
         
         try: 
              #call the telnet_login to access the server through telnet
               child = telnet_login(self.server_name_,self.user_, self.passwd_) 
         except RuntimeError,ex: 
             info = "telnet to machine %s failed with reason %s" % (self.server_name_, ex)
self.result_.=(False, self.server_name_+info)
             return self.result_  
          
         child.sendline(' cd ~/Download/dw_test && \
               wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.28.tar.gz && \
               tar zxvf linux-2.6.28.tar.gz && \
               cd linux-2.6.28 \
               && make mrproper && make allyesconfig and make -j 4 && make modules && \
               make modules install && make install') 
         
         # wail these commands finish
         while True:
             index = child.expect([PROMPT,pexpect.TIMEOUT,pexpect.EOF]) 
             if index == 0:
                  break
             elif index == 1:
                 pass
             elif index ==2 :
                 self.result_=(False,'Sub process exit abnormally ')
             return False
 
         # reboot the server
         child.sendline('shutdown -Fr') 
         child.expect('\r\n') 
         retry_times = 10
         while retry_times > 0:  
             index_shutdown = child.expect(["Unmounting the file systems",
                                            pexpect.EOF, 
                                            pexpect.TIMEOUT]) 
             if index_shutdown == 0 or index_shutdown == 1 : 
                 break 
             elif index_shutdown == 2:
                 retry_times = retry_times-1
                 if retry_times == 0: 
                     self.result_=(False,'Cannot shutdown ') 
                     return self.result_
                       
                
def refresh_kernel(linux_server_list,same_user,same_passwd): 
     """ 
     @summary: The function is used to work on different linux servers to download
     the same version linux kernel, conpile them and reboot all these servers 
     To keep it simple we use the same user id and password on these servers   
     """ 
     if not type(linux_server_list) == list:
         return (False,"Param %s Error!"%linux_server_list) 
      
     if same_user is None or same_passwd is None or not 
     type(same_user)== str or not type(same_passwd) == str: 
         return (False,"Param Error!")
      
     thread_list = []
     # start threads to execute command on the remote servers
     for i in range (len(linux_server_list)): 
         thread_list[i] = RefreshKernelThreadClass(linux_server_list[i],
                                                   same_user,same_passwd)
         thread_list[i].start()
     
     # wait the threads finish
     for i in range (len(linux_server_list)): 
         thread_list[i].join()
     # validate the result
     for i in range (len(linux_server_list)): 
         if thread_list[0].result_[0] == False: 
             return False
         else:
             return True
  
if __name__ == "__main__":
     refresh_kernel(server_list,"test_user","test_passwd")

相關主題

 

探索 Pexpect,第 2 部分:Pexpect 的實例分析


概述

經過本系列第一部分《探索 Pexpect,第 1 部分:剖析 Pexpect 》(請參閱參考資料)的介紹,相信你們已經對 Pexpect 的用法已經有了比較全面的瞭解,知道 Pexpect 是個純 Python 語言實現的模塊,使用其能夠輕鬆方便的實現與 ssh、ftp、passwd 和 telnet 等程序的自動交互,可是讀者的理解還可能只是停留在理論基礎上,本文將從實際例子入手具體介紹 Pexpect 的使用場景和使用心得體驗,實例中的代碼讀者均可以直接拿來使用,相信會對你們產生比較大的幫助。 如下是本文所要介紹的全部 Pexpect 例子標題:

  • 例 1:ftp 的使用(注:spawn、expect 和 sendline 的使用)
  • 例 2:記錄 log(注:logfile、logfile_sendlogfile_read的使用)
  • 例 3:ssh 的使用
  • 例 4:pxssh 的使用
  • 例 5:telnet 的使用(注:interact 的使用)
  • pexpect 使用 tips
    • 調試 pexpect 程序的 tips
    • pexpect 不會解釋 shell 中的元字符
    • EOF 異常和 TIMEOUT 異常
    • 使用 run() 來替代某些的 spawn 的使用
    • expect_exact() 的使用
    • expect() 中正則表達式的使用 tips
    • isalive() 的使用 tips
    • delaybeforesend 的使用 tips

例 1:ftp 的使用

本例實現了以下功能:ftp 登陸到 develperWorks.ibm.com 主機上,並用二進制傳輸模式下載一個名叫 rmall的文件。

清單 1. ftp 的例子代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#!/usr/bin/env python
 
import pexpect
# 即將 ftp 所要登陸的遠程主機的域名
ipAddress = 'develperWorks.ibm.com'
# 登陸用戶名
loginName = 'root'
# 用戶名密碼
loginPassword = 'passw0rd'
 
# 拼湊 ftp 命令
cmd = 'ftp ' + ipAddress
# 利用 ftp 命令做爲 spawn 類構造函數的參數,生成一個 spawn 類的對象
child = pexpect.spawn(cmd)
# 指望具備提示輸入用戶名的字符出現
index = child.expect(["(?i)name", "(?i)Unknown host", pexpect.EOF, pexpect.TIMEOUT])
# 匹配到了 "(?i)name",代表接下來要輸入用戶名
if ( index == 0 ):
     # 發送登陸用戶名 + 換行符給子程序.
     child.sendline(loginName)
     # 指望 "(?i)password" 具備提示輸入密碼的字符出現.
     index = child.expect(["(?i)password", pexpect.EOF, pexpect.TIMEOUT])
     # 匹配到了 pexpect.EOF 或 pexpect.TIMEOUT,表示超時或者 EOF,程序打印提示信息並退出.
     if (index != 0):
         print "ftp login failed"
         child.close(force=True)
     # 匹配到了密碼提示符,發送密碼 + 換行符給子程序.
     child.sendline(loginPassword)
     # 指望登陸成功後,提示符 "ftp>" 字符出現.
     index = child.expect( ['ftp>', 'Login incorrect', 'Service not available',
     pexpect.EOF, pexpect.TIMEOUT])
     # 匹配到了 'ftp>',登陸成功.
     if (index == 0):
         print 'Congratulations! ftp login correct!'
         # 發送 'bin'+ 換行符給子程序,表示接下來使用二進制模式來傳輸文件.
         child.sendline("bin")
         print 'getting a file...'
         # 向子程序發送下載文件 rmall 的命令.
         child.sendline("get rmall")
         # 指望下載成功後,出現 'Transfer complete.*ftp>',其實下載成功後,
         # 會出現如下相似於如下的提示信息:
         #    200 PORT command successful.
         #    150 Opening data connection for rmall (548 bytes).
         #    226 Transfer complete.
         #    548 bytes received in 0.00019 seconds (2.8e+03 Kbytes/s)
         # 因此直接用正則表達式 '.*' 將 'Transfer complete' 和提示符 'ftp>' 之間的字符全省去.
         index = child.expect( ['Transfer complete.*ftp>', pexpect.EOF, pexpect.TIMEOUT] )
         # 匹配到了 pexpect.EOF 或 pexpect.TIMEOUT,表示超時或者 EOF,程序打印提示信息並退出.
         if (index != 0):
             print "failed to get the file"
             child.close(force=True)
         # 匹配到了 'Transfer complete.*ftp>',代表下載文件成功,打印成功信息,並輸入 'bye',結束 ftp session.
         print 'successfully received the file'
         child.sendline("bye")
     # 用戶名或密碼不對,會先出現 'Login incorrect',而後仍會出現 'ftp>',可是 pexpect 是最小匹配,不是貪婪匹配,
     # 因此若是用戶名或密碼不對,會匹配到 'Login incorrect',而不是 'ftp>',而後程序打印提示信息並退出.
     elif (index == 1):
         print "You entered an invalid login name or password. Program quits!"
         child.close(force=True)
     # 匹配到了 'Service not available',通常代表 421 Service not available, remote server has
     # closed connection,程序打印提示信息並退出.
     # 匹配到了 pexpect.EOF 或 pexpect.TIMEOUT,表示超時或者 EOF,程序打印提示信息並退出.
     else:
         print "ftp login failed! index = " + index
         child.close(force=True)
 
 
# 匹配到了 "(?i)Unknown host",表示 server 地址不對,程序打印提示信息並退出
elif index == 1 :
     print "ftp login failed, due to unknown host"
     child.close(force=True)
# 匹配到了 pexpect.EOF 或 pexpect.TIMEOUT,表示超時或者 EOF,程序打印提示信息並退出
else:
     print "ftp login failed, due to TIMEOUT or EOF"
     child.close(force=True)

注:

  • 運行後,輸出結果爲:
1
2
3
Congratulations! ftp login correct!
getting a file...
successfully received the file
  • 本例 expect 函數中的 pattern 使用了 List,幷包含了 pexpect.EOF和pexpect.TIMEOUT,這樣出現了超時或者 EOF,不會拋出 expection 。(關於 expect() 函數的具體使用,請參閱參考資料)
  • 若是程序運行中間出現了錯誤,如用戶名密碼錯誤,超時或者 EOF,遠程 server 鏈接不上,都會使用 c hild.close(force=True) 關掉 ftp 子程序。調用 close 能夠用來關閉與子程序的 connection 鏈接,若是你不只想關閉與子程序的鏈接,還想確保子程序是真的被 terminate 終止了,設置參數 force=True,其最終會調用 c hild.kill(signal.SIGKILL) 來殺掉子程序。

例 2:記錄 log

本例實現了以下功能:運行一個命令,並將該命令的運行輸出結果記錄到 log 文件中 ./command.py [-a] [-c command] {logfilename} -c 後接的是要運行的命令的名字,默認是「ls -l」; logfilename 是記錄命令運行結果的 log 文件名,默認是「command.log」;指定 -a 表示命令的輸出結果會附加在 logfilename 後,若是 logfilename 以前已經存在的話。

清單 2. 記錄 log 的例子代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#!/usr/bin/env python
"""
This run a user specified command and log its result.
 
./command.py [-a] [-c command] {logfilename}
 
logfilename : This is the name of the log file. Default is command.log.
-a : Append to log file. Default is to overwrite log file.
-c : spawn command. Default is the command 'ls -l'.
 
Example:
 
This will execute the command 'pwd' and append to the log named my_session.log:
 
./command.py -a -c 'pwd' my_session.log
 
"""
import os, sys, getopt
import traceback
import pexpect
 
# 若是程序中間出錯,打印提示信息後退出
def exit_with_usage():
     print globals()['__doc__']
     os._exit(1)
 
def main():
     ######################################################################
     # Parse the options, arguments, get ready, etc.
     ######################################################################
     try:
         optlist, args = getopt.getopt(sys.argv[1:], 'h?ac:', ['help','h','?'])
     # 若是指定的參數不是’ -a ’ , ‘ -h ’ , ‘ -c ’ , ‘ -? ’ , ‘ --help ’ ,
     #‘ --h ’或’ --? ’時,會拋出 exception,
     # 這裏 catch 住,而後打印出 exception 的信息,並輸出 usage 提示信息.
     except Exception, e:
         print str(e)
         exit_with_usage()
     options = dict(optlist)
     # 最多隻能指定一個 logfile,不然出錯.
     if len(args) > 1:
         exit_with_usage()
     # 若是指定的是 '-h','--h','-?','--?' 或 '--help',只輸出 usage 提示信息.
     if [elem for elem in options if elem in ['-h','--h','-?','--?','--help']]:
         print "Help:"
         exit_with_usage()
     # 獲取 logfile 的名字.
     if len(args) == 1:
         script_filename = args[0]
     else:
     # 若是用戶沒指定,默認 logfile 的名字是 command.log
         script_filename = "command.log"
     # 若是用戶指定了參數 -a,若是以前該 logfile 存在,那麼接下來的內容會附加在原先內容以後,
     # 若是以前沒有該  logfile,新建一個文件,而且接下來將內容寫入到該文件中.
     if '-a' in options:
         fout = open (script_filename, "ab")
     else:
     # 若是用戶沒指定參數 -a,默認按照用戶指定 logfile 文件名新建一個文件,而後將接下來將內容寫入到該文件中.
         fout = open (script_filename, "wb")
     # 若是用戶指定了 -c 參數,那麼運行用戶指定的命令.
     if '-c' in options:
         command = options['-c']
     # 若是用戶沒有指定 -c 參數,那麼默認運行命令'ls – l'
     else:
         command = "ls -l"
 
     # logfile 文件的 title
     fout.write ('==========Log Tile: IBM developerWorks China==========\n')
 
     # 爲接下來的運行命令生成一個 pexpect 的 spawn 類子程序的對象.
     p = pexpect.spawn(command)
     # 將以前 open 的 file 對象指定爲 spawn 類子程序對象的 log 文件.
     p.logfile = fout
     # 命令運行完後,expect EOF 出現,這時會將 spawn 類子程序對象的輸出寫入到 log 文件.
     p.expect(pexpect.EOF)
     #open 完文件,使用完畢後,需關閉該文件.
     fout.close()
     return 0
 
if __name__ == "__main__":
     try:
         main()
     except SystemExit, e:
         raise e
     except Exception, e:
         print "ERROR"
         print str(e)
         traceback.print_exc()
         os._exit(1)

注:

  • 運行:./command.py -a -c who cmd.log
1
2
3
4
5
運行結束後,cmd.log 的內容爲:
IBM developerWorks China
Root     :0          2009-05-12 22:40
Root     pts/1       2009-05-12 22:40 (:0.0)
Root     pts/2       2009-07-05 18:55 (9.77.180.94)
  • logfile

只能經過 spawn 類的構造函數指定。在 spawn 類的構造函數經過參數指定 logfile 時,表示開啓或關閉 logging 。全部的子程序的 input 和 output 都會被 copy 到指定的 logfile 中。設置 logfile 爲 None 表示中止 logging,默認就是中止 logging 。設置 logfile 爲 sys.stdout,會將全部東西 echo 到標準輸出。

  • logfile_readlogfile_send:

logfile_read:只用來記錄 python 主程序接收到 child 子程序的輸出,有的時候你不想看到寫給 child 的全部東西,只但願看到 child 發回來的東西。 logfile_send:只用來記錄 python 主程序發送給 child 子程序的輸入 logfile、logfile_read 和 logfile_send 什麼時候被寫入呢? logfile、logfile_read 和 logfile_send 會在每次寫 write 和 send 操做後被 flush 。

    • 調用 send 後,纔會往 logfile 和 logfile_send 中寫入,sendline/sendcontrol/sendoff/write/writeline 最終都會調用 send,因此 sendline 後 logfile 中必定有內容了,只要此時 logfile 沒有被 close 。
    • 調用 read_nonblocking 後,纔會往 logfile 和 logfile_read 中寫入,expect_loop 會調用 read_nonblocking,而 expect_exact 和 expect_list 都會調用 expect_loop,expect 會調用 expect_list,因此 expect 後 logfile 中必定有內容了,只要此時 logfile 沒有被 close 。
    若是調用的函數最終都沒有調用 send 或 read_nonblocking,那麼 logfile 雖然被分配指定了一個 file,但其最終結果是:內容爲空。見下例:
清單 3. log 內容爲空的例子代碼
1
2
3
4
5
import pexpect
p = pexpect.spawn( ‘ ls -l ’ )
fout = open ('log.txt', "w")
p.logfile = fout
fout.close()

運行該腳本後,你會發現其實 log.txt 是空的,沒有記錄 ls -l 命令的內容,緣由是沒有調用 send 或 read_nonblocking,真正的內容沒有被 flush 到 log 中。若是在 fout.close() 以前加上 p.expect(pexpect.EOF),log.txt 纔會有 ls -l 命令的內容。

例 3:ssh 的使用

本例實現了以下功能:ssh 登陸到某個用戶指定的主機上,運行某個用戶指定的命令,並輸出該命令的結果。

清單 4. ssh 的例子代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#!/usr/bin/env python
 
"""
This runs a command on a remote host using SSH. At the prompts enter hostname,
user, password and the command.
"""
 
import pexpect
import getpass, os
 
#user: ssh 主機的用戶名
#host:ssh 主機的域名
#password:ssh 主機的密碼
#command:即將在遠端 ssh 主機上運行的命令
def ssh_command (user, host, password, command):
     """
     This runs a command on the remote host. This could also be done with the
     pxssh class, but this demonstrates what that class does at a simpler level.
     This returns a pexpect.spawn object. This handles the case when you try to
     connect to a new host and ssh asks you if you want to accept the public key
     fingerprint and continue connecting.
     """
     ssh_newkey = 'Are you sure you want to continue connecting'
     # 爲 ssh 命令生成一個 spawn 類的子程序對象.
     child = pexpect.spawn('ssh -l %s %s %s'%(user, host, command))
     i = child.expect([pexpect.TIMEOUT, ssh_newkey, 'password: '])
     # 若是登陸超時,打印出錯信息,並退出.
     if i == 0: # Timeout
         print 'ERROR!'
         print 'SSH could not login. Here is what SSH said:'
         print child.before, child.after
         return None
     # 若是 ssh 沒有 public key,接受它.
     if i == 1: # SSH does not have the public key. Just accept it.
         child.sendline ('yes')
         child.expect ('password: ')
         i = child.expect([pexpect.TIMEOUT, 'password: '])
         if i == 0: # Timeout
         print 'ERROR!'
         print 'SSH could not login. Here is what SSH said:'
         print child.before, child.after
         return None
     # 輸入密碼.
     child.sendline(password)
     return child
 
def main ():
     # 得到用戶指定 ssh 主機域名.
     host = raw_input('Hostname: ')
     # 得到用戶指定 ssh 主機用戶名.
     user = raw_input('User: ')
     # 得到用戶指定 ssh 主機密碼.
     password = getpass.getpass()
     # 得到用戶指定 ssh 主機上即將運行的命令.
     command = raw_input('Enter the command: ')
     child = ssh_command (user, host, password, command)
     # 匹配 pexpect.EOF
     child.expect(pexpect.EOF)
     # 輸出命令結果.
     print child.before
 
if __name__ == '__main__':
     try:
         main()
     except Exception, e:
         print str(e)
         traceback.print_exc()
         os._exit(1)

注:

  • 運行後,輸出結果爲:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Hostname: develperWorks.ibm.com
User: root
Password:
Enter the command: ls -l
 
total 60
drwxr-xr-x   2 root      system      512 Jun 14 2006  .dt
drwxrwxr-x   3 root      system      512 Sep 23 2008  .java
-rwx------   1 root      system      1855 Jun 14 2006  .kshrc
-rwx------   1 root      system      806 Sep 16 2008  .profile
-rwx------   1 root      system      60 Jun 14 2006  .rhosts
drwx------   2 root      system      512 Jan 18 2007  .ssh
drwxr-x---   2 root      system      512 Apr 15 00:04 223002
-rwxr-xr-x   1 root      system      120 Jan 16 2007  drcron.sh
-rwx------   1 root      system      10419 Jun 14 2006  firewall
drwxr-x---   2 root      system      512 Oct 25 2007  jre
-rw-------   1 root      system      3203 Apr 04 2008  mbox
-rw-r--r--   1 root      system      0 Jun 14 2006  pt1
-rw-r--r--   1 root      system      0 Jun 14 2006  pt2
  • 使用了 getpass.getpass() 來得到用戶輸入的密碼,與 raw_input 不一樣的是,getpass.getpass() 不會將用戶輸入的密碼字符串 echo 回顯到 stdout 上。(更多 python 相關技術,請參閱參考資料)

例 4:pxssh 的使用

本例實現了以下功能:使用 pexpect 自帶的 pxssh 模塊實現 ssh 登陸到某個用戶指定的主機上,運行命令’ uptime ’和’ ls -l ’,並輸出該命令的結果。

清單 5. 使用 pxssh 的例子代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env python
import pxssh
import getpass
try:
     # 調用構造函數,建立一個 pxssh 類的對象.
     s = pxssh.pxssh()
     # 得到用戶指定 ssh 主機域名.
     hostname = raw_input('hostname: ')
     # 得到用戶指定 ssh 主機用戶名.
     username = raw_input('username: ')
     # 得到用戶指定 ssh 主機密碼.
     password = getpass.getpass('password: ')
     # 利用 pxssh 類的 login 方法進行 ssh 登陸,原始 prompt 爲'$' , '#'或'>'
     s.login (hostname, username, password, original_prompt='[$#>]')
     # 發送命令 'uptime'
     s.sendline ('uptime')
     # 匹配 prompt
     s.prompt()
     # 將 prompt 前全部內容打印出,即命令 'uptime' 的執行結果.
     print s.before
     # 發送命令 ' ls -l '
     s.sendline ('ls -l')
     # 匹配 prompt
     s.prompt()
     # 將 prompt 前全部內容打印出,即命令 ' ls -l ' 的執行結果.
     print s.before
     # 退出 ssh session
     s.logout()
except pxssh.ExceptionPxssh, e:
     print "pxssh failed on login."
     print str(e)
  • 運行後,輸出結果爲:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
hostname: develperWorks.ibm.com
username: root
password:
uptime
02:19AM   up 292 days,  12:16,  2 users,  load average: 0.01, 0.02, 0.01
 
ls -l
total 60
drwxr-xr-x   2 root      system      512 Jun 14 2006  .dt
drwxrwxr-x   3 root      system      512 Sep 23 2008  .java
-rwx------   1 root      system      1855 Jun 14 2006  .kshrc
-rwx------   1 root      system      806 Sep 16 2008  .profile
-rwx------   1 root      system      60 Jun 14 2006  .rhosts
drwx------   2 root      system      512 Jan 18 2007  .ssh
drwxr-x---   2 root      system      512 Apr 15 00:04 223002
-rwxr-xr-x   1 root      system      120 Jan 16 2007  drcron.sh
-rwx------   1 root      system      10419 Jun 14 2006  firewall
drwxr-x---   2 root      system      512 Oct 25 2007  jre
-rw-------   1 root      system      3203 Apr 04 2008  mbox
-rw-r--r--   1 root      system      0 Jun 14 2006  pt1
-rw-r--r--   1 root      system      0 Jun 14 2006  pt2
  • pxssh 是 pexpect 中 spawn 類的子類,增長了 login, logout 和 prompt 幾個方法,使用其能夠輕鬆實現 ssh 鏈接,而不用本身調用相對複雜的 pexpect 的方法來實現。 pxssh 作了不少 tricky 的東西來處理 ssh login 過程當中所可能遇到的各類狀況。好比:若是這個 session 是第一次 login,pxssh 會自動接受遠程整數 remote certificate ;若是你已經設置了公鑰認證,pxssh 將不會再等待 password 的提示符。(更多 ssh 相關知識,請參閱參考資料) pxssh 使用 shell 的提示符來同步遠程主機的輸出,爲了使程序更加穩定,pxssh 還能夠設置 prompt 爲更加惟一的字符串,而不只僅是「 $ 」和「 # 」。
  • login 方法
1
2
login (self,server,username,password='',terminal_type='ansi',
   iginal_prompt=r"[#$]",login_timeout=10,port=None,auto_prompt_reset=True):

使用原始 original_prompt 來找到 login 後的提示符(這裏默認 original_prompt 是「$」或「#」,可是有時候可能也是別的 prompt,這時就須要在 login 時手動指定這個特殊的 prompt,見上例,有多是「 > 」),若是找到了,立馬使用更容易匹配的字符串來重置該原始提示符(這是由 pxssh 本身自動作的,經過命令 "PS1='[PEXPECT]\$ '" 重置原始提示符,而後每次 expect 匹配 \[PEXPECT\][\$\#])。原始提示符是很容易被混淆和胡弄的,爲了阻止錯誤匹配,最好根據特定的系統,指定更加精確的原始提示符,例如 "Message Of The Day" 。 有些狀況是不容許重置原始提示符的,這時就要設置 auto_prompt_reset 爲 False 。並且此時須要手動設置 PROMPT 域爲某個正則表達式來 match 接下來要出現的新提示符,由於 prompt() 函數默認是 expect 被重置過的 PROMPT 的。

  • prompt方法
1
prompt (self, timeout=20):

匹配新提示符(不是 original_prompt)。注:這只是匹配提示符,不能匹配別的 string,若是要匹配特殊 string,需直接使用父類 spawn 的 expect 方法。 prompt 方法至關因而 expect 方法的一個快捷方法。若是auto_prompt_reset 爲 False,這時須要手動設置 PROMPT 域爲某個正則表達式來 match 接下來要出現的 prompt,由於 prompt() 函數默認是 expect 被重置過的 PROMPT 的。

  • logout方法
1
logout (self):

發送'exit'給遠程 ssh 主機,若是有 stopped jobs,會發送'exit'兩次。

例 5:telnet 的使用

本例實現了以下功能:telnet 登陸到某遠程主機上,輸入命令「ls -l」後,將子程序的執行權交還給用戶,用戶能夠與生成的 telnet 子程序進行交互。

清單 6. telnet 的例子代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/usr/bin/env python
import pexpect
 
# 即將 telnet 所要登陸的遠程主機的域名
ipAddress = 'develperWorks.ibm.com'
# 登陸用戶名
loginName = 'root'
# 用戶名密碼
loginPassword = 'passw0rd'
# 提示符,多是’ $ ’ , ‘ # ’或’ > ’
loginprompt = '[$#>]'
 
# 拼湊 telnet 命令
cmd = 'telnet ' + ipAddress
# 爲 telnet 生成 spawn 類子程序
child = pexpect.spawn(cmd)
# 期待'login'字符串出現,從而接下來能夠輸入用戶名
index = child.expect(["login", "(?i)Unknown host", pexpect.EOF, pexpect.TIMEOUT])
if ( index == 0 ):
     # 匹配'login'字符串成功,輸入用戶名.
     child.sendline(loginName)
     # 期待 "[pP]assword" 出現.
     index = child.expect(["[pP]assword", pexpect.EOF, pexpect.TIMEOUT])
     # 匹配 "[pP]assword" 字符串成功,輸入密碼.
     child.sendline(loginPassword)
     # 期待提示符出現.
     child.expect(loginprompt)
     if (index == 0):
         # 匹配提示符成功,輸入執行命令 'ls -l'
         child.sendline('ls -l')
         # 立馬匹配 'ls -l',目的是爲了清除剛剛被 echo 回顯的命令.
         child.expect('ls -l')
         # 期待提示符出現.
         child.expect(loginprompt)
         # 將 'ls -l' 的命令結果輸出.
         print child.before
         print "Script recording started. Type ^] (ASCII 29) to escape from the script
               shell."
         # 將 telnet 子程序的執行權交給用戶.
         child.interact()
         print 'Left interactve mode.'
     else:
         # 匹配到了 pexpect.EOF 或 pexpect.TIMEOUT,表示超時或者 EOF,程序打印提示信息並退出.
         print "telnet login failed, due to TIMEOUT or EOF"
         child.close(force=True)
else:
     # 匹配到了 pexpect.EOF 或 pexpect.TIMEOUT,表示超時或者 EOF,程序打印提示信息並退出.
     print "telnet login failed, due to TIMEOUT or EOF"
     child.close(force=True)
  • 運行後,輸出結果爲:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
total 60
drwxr-xr-x   2 root     system          512 Jun 14 2006  .dt
drwxrwxr-x   3 root     system          512 Sep 23 2008  .java
-rwx------   1 root     system         1855 Jun 14 2006  .kshrc
-rwx------   1 root     system          806 Sep 16 2008  .profile
-rwx------   1 root     system           60 Jun 14 2006  .rhosts
drwx------   2 root     system          512 Jan 18 2007  .ssh
drwxr-x---   2 root     system          512 Apr 15 00:04 223002
-rwxr-xr-x   1 root     system          120 Jan 16 2007  drcron.sh
-rwx------   1 root     system        10419 Jun 14 2006  firewall
drwxr-x---   2 root     system          512 Oct 25 2007  jre
-rw-------   1 root     system         3203 Apr 04 2008  mbox
-rw-r--r--   1 root     system            0 Jun 14 2006  pt1
-rw-r--r--   1 root     system            0 Jun 14 2006  pt2
essni2
Script recording started. Type ^] (ASCII 29) to escape from the script shell.
此時程序會 block 住,等待用戶的輸入,好比用戶輸入’ pwd ’,輸出/home/root
接下來用戶敲入 ctrl+] 結束子程序
  • interact方法
1
interact(self, escape_character = chr(29), input_filter = None, output_filter = None)

一般一個 python 主程序經過 pexpect.spawn 啓動一個子程序,一旦該子程序啓動後,python 主程序就能夠經過 child.expect 和 child.send/child.sendline 來和子程序通話,python 主程序運行結束後,子程序也就死了。好比 python 主程序經過 pexpect.spawn 啓動了一個 telnet 子程序,在進行完一系列的 telnet 上的命令操做後,python 主程序運行結束了,那麼該 telnet session(telnet 子程序)也會自動退出。可是若是調用 child.interact,那麼該子程序(python 主程序經過 pexpect.spawn 衍生成的)就能夠在運行到 child.interact 時,將子程序的控制權交給了終端用戶(the human at the keyboard),用戶能夠經過鍵盤的輸入來和子程序進行命令交互,管理子程序的生殺大權,用戶的鍵盤輸入 stdin 會被傳給子程序,並且子程序的 stdout 和 stderr 輸出也會被打印出來到終端。 默認 ctrl + ] 退出 interact() 模式,把子程序的執行權從新交給 python 主程序。參數 escape_character 指定了交互模式的退出字符,例如 child.interact(chr(26)) 接下來就會變成 ctrl + z 退出 interact() 模式。

pexpect 使用 tips

調試 pexpect 程序的 tips

  • 得到 pexpect.spawn 對象的字符串 value值,將會給 debug 提供不少有用信息。
清單 7. 打印 pexpect.spawn 對象的字符串 value 值的例子代碼
1
2
3
4
5
6
try:
     i = child.expect ([pattern1, pattern2, pattern3, etc])
except:
     print "Exception was thrown"
     print "debug information:"
     print str(child)
  • 將子程序的 input 和 output 打 log 到文件中或直接打 log 到屏幕上也很是有用
清單 8. 記錄 log 的例子代碼
1
2
3
# 打開 loggging 功能並將結果輸出到屏幕上
child = pexpect.spawn (foo)
child.logfile = sys.stdout

pexpect 不會解釋 shell 中的元字符

  • pexpect 不會解釋 shell 的元字符,如重定向 redirect,管道 pipe,和通配符 wildcards( 「 > 」 , 「 | 」和「 * 」等 ) 若是想用的話,必須得從新啓動一個新 shell(在 spawn 的參數 command 中是不會解釋他們的,視其爲 command string 的一個普通字符)
清單 9. 從新啓動一個 shell 來規避 pexpect 對元字符的不解釋
1
2
child = pexpect.spawn('/bin/bash -c "ls -l | grep LOG > log_list.txt"')
child.expect(pexpect.EOF)

若是想在 spawn 出來的新子程序中使用重定向 redirect,管道 pipe,和通配符 wildcards( 「 > 」 , 「 | 」和「 * 」等 ),好像沒有好的方法,只能不使用這些字符,先利用 expect 匹配命令提示符,從而在 before 中能夠拿到以前命令的結果,而後在分析 before 的內容達到使用重定向 redirect, 管道 pipe, 和通配符 wildcards 的目的。

EOF 異常和 TIMEOUT 異常

  • TIMEOUT 異常

若是子程序沒有在指定的時間內生成任何 output,那麼 expect() 和 read() 都會產生 TIMEOUT 異常。超時默認是 30s,能夠在 expect() 和 spawn 構造函數初始化時指定爲其它時間,如:

1
child.expect('password:', timeout=120) # 等待 120s

若是你想讓 expect() 和 read() 忽略超時限制,即無限期阻塞住直到有 output 產生,設置 timeout 參數爲 None。

清單 10. 忽略 timeout 超時限制的例子代碼
1
2
child = pexpect.spawn( "telnet develperWorks.ibm.com" )
child.expect( "login", timeout=None )
  • EOF 異常

可能會有兩種 EOF 異常被拋出,可是他們除了顯示的信息不一樣,其實本質上是相同的。爲了實用的目的,不須要區分它們,他們只是給了些關於你的 python 程序到底運行在哪一個平臺上的額外信息,這兩個顯示信息是:

1
2
End Of File (EOF) in read(). Exception style platform.
End Of File (EOF) in read(). Empty string style platform.

有些 UNIX 平臺,當你讀取一個處於 EOF 狀態的文件描述符時,會拋出異常,其餘 UNIX 平臺,卻只會靜靜地返回一個空字符串來代表該文件已經達到了狀態。

使用 run() 來替代某些的 spawn 的使用

pexpect 模塊除了提供 spawn 類之外,還提供了 run() 函數,使用其能夠取代一些 spawn 的使用,並且更加簡單明瞭。

清單 11. 使用 run() 來替代 spawn 的使用的例子代碼
1
2
3
4
5
6
7
8
# 使用 spawn 的例子
from pexpect import *
child = spawn('scp foo myname@host.example.com:.')
child.expect ('(?i)password')
child.sendline (mypassword)
# 以上功能,至關於如下 run 函數:
from pexpect import *
run ('scp foo myname@host.example.com:.', events={'(?i)password': mypassword})
  • run (command, timeout=-1, withexitstatus=False, events=None, extra_args=None, logfile=None, cwd=None, env=None):
    • command:執行一個命令,而後返回結果,run() 能夠替換 os.system()(更多 os.system() 知識,請參閱參考資料),由於 os.system() 得不到命令輸出的結果
    • 返回的 output 是個字符串,STDERR 也會包括在 output 中,若是全路徑沒有被指定,那麼 path 會被 search
    • timeout:單位 s 秒,每隔 timeout 生成一個 pexpect.TIMEOUT 異常
    • 每行之間被 CR/LF (\\r\\n) 相隔,即便在 Unix 平臺上也是 CR/LF,由於 Pexpect 子程序是僞 tty 設備
    • withexitstatus:設置爲 True,則返回一個 tuple,裏面包括 (command_output, exitstatus),若是其爲 False,那麼只是僅僅返回 command_output
    • events:是個 dictionary,裏面存放 {pattern:response} 。不管何時 pattern 在命令的結果中出現了,會出現如下動做:
      • 發送相應的 response String 。若是須要回車符「 Enter 」的話,「 \\n 」也必須得出如今 response 字符串中。
      • response 一樣也能夠是個回調函數,不過該回調函數有特殊要求,即它的參數必須是個 dictionary,該 dictionary 的內容是:包含全部在 run() 中定義的局部變量,從而提供了方法能夠訪問 run() 函數中 spawn 生成的子程序和 run() 中定義的其餘局部變量,其中 event_count, child, 和 extra_args 最有用。回調函數可能返回 True,從而阻止當前 run() 繼續執行,不然 run() 會繼續執行直到下一個 event 。回調函數也可能返回一個字符串,而後被髮送給子程序。 'extra_args' 不是直接被 run() 使用,它只是提供了一個方法能夠經過 run() 來將數據傳入到回調函數中(實際上是經過 run() 定義的局部變量 dictionary 來傳)
清單 12. 其它一些使用 run() 的例子代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 在 local 機器上啓動 apache 的 daemon
from pexpect import *
run ("/usr/local/apache/bin/apachectl start")
# 使用 SVN check in 文件
from pexpect import *
run ("svn ci -m 'automatic commit' my_file.py")
# 運行一個命令並捕獲 exit status
from pexpect import *
command_output, exitstatus) = run ('ls -l /bin', withexitstatus=1)
# 運行 SSH,並在遠程機器上執行’ ls -l ’,若是 pattern '(?i)password' 被匹配住,密碼 'secret'
# 將會被髮送出去
run ("ssh username@machine.example.com 'ls -l'", events={'(?i)password':'secret\\n'})
# 啓動 mencoder 來 rip 一個 video,一樣每 5s 鍾顯示進度記號
from pexpect import *
def print_ticks(d):
     print d['event_count']
run ("mencoder dvd://1 -o video.avi -oac copy -ovc copy", events={TIMEOUT:print_ticks})

expect_exact() 的使用

expect_exact(self, pattern_list, timeout = -1, searchwindowsize = -1); expect_exact() 與 expect() 相似,可是 pattern_list 只能是字符串或者是一個字符串的 list,不能是正則表達式,其匹配速度會快於 expect(),緣由有兩個:一是字符串的 search 比正則表達式的匹配要快,另外一個則是能夠限制只從輸入緩衝的結尾來尋找匹配的字符串。還有當你以爲每次要 escape 正則表達式中的特殊字符爲普通字符時很煩,那麼你也可使用 expect_exact() 來取代 expect()。

清單 13. expect_exact() 的例子代碼
1
2
3
4
import pexpect
child = pexpect.spawn('ls -l')
child.expect_exact('pexpect.txt')
print child.after

expect() 中正則表達式的使用 tips

expect() 中的正則表達式不是貪婪匹配 greedy match,而是最小匹配,即只匹配緩衝區中最先出現的第一個字符串。 由於是依次讀取一個字符的 stream 流來判斷是否和正則表達式所表達的模式匹配,因此若是參數 pattern 是個 list,並且不止一次匹配,那麼緩衝區中最先出現的第一個匹配的字符串纔算數。

清單 14. expect() 的最小匹配例子代碼
1
2
3
4
# 若是輸入是 'foobar'
index = p.expect (['bar', 'foo', 'foobar'])
#index 返回是 1 ('foo') 而不是 2 ('foobar'),即便 'foobar' 是個更好的匹配。緣由是輸入是個流 stream,
# 當收到 foo 時,第 1 個 pattern ('foo') 就被匹配了,不會等到’ bar ’的出現了,因此返回 1
  • 「$」不起任何做用,匹配一行的結束 (end of line),必須得匹配 CR/LF

正則表達式中,'$'能夠匹配一行的結束(具體'$'正則表達式的使用,請參閱參考資料),可是 pexpect 從子程序中一次只讀取一個字符,並且每一個字符都好像是一行的結束同樣,pexpect 不能在子程序的輸出流去預測。匹配一行結束的方法必須是匹配 "\r\n" (CR/LF) 。即便是 Unix 系統,也是匹配 "\r\n" (CR/LF),由於 pexpect 使用一個 Pseudo-TTY 設備與子程序通話,因此當子程序輸出 "\n" 你仍然會在 python 主程序中看到 "\r\n" 。緣由是 TTY 設備更像 windows 操做系統,每一行結束都有個 "\r\n" (CR/LF) 的組合,當你從 TTY 設備去解釋一個 Unix 的命令時,你會發現真正的輸出是 "\r\n" (CR/LF),一個 Unix 命令只會寫入一個 linefeed (\n),可是 TTY 設備驅動會將其轉換成 "\r\n" (CR/LF) 。

清單 15. 匹配一行結束 1
1
child.expect ('\r\n')

若是你只是想跳過一個新行,直接 expect('\n') 就能夠了,可是若是你想在一行的結束匹配一個具體的 pattern 時,就必須精確的尋找 (\r),見下例:

清單 16. 匹配一行結束 2
1
2
3
4
5
# 成功在一行結束前匹配一個單詞
child.expect ('\w+\r\n')
# 如下兩種狀況都會失敗
child.expect ('\w+\n')
child.expect ('\w+$')

這個問題其實不僅是 pexpect 會有,若是你在一個 stream 流上實施正則表達式匹配時,都會遇到此問題。正則表達式須要預測,stream 流中很難預測,由於生成這個流的進程可能尚未結束,因此你很難知道是否該進程是暫時性的暫停仍是已經完全結束。

  • 當 '.' 和 '*' 出如今最後時

child.expect ('.+'); 由於是最小匹配,因此只會返回一個字符,而不是一個整個一行(雖然 pexpect 設置了 re.DOTALL,會匹配一個新行。 child.expect ('.*'); 每次匹配都會成功,可是老是沒有字符返回,由於 '*' 代表前面的字符能夠出現 0 次 , 在 pexpect 中,通常來講,任何 '*' 都會盡可能少的匹配。

isalive() 的使用 tips

  • isalive(self)

測試子程序是否還在運行。這個方法是非阻塞的,若是子程序被終止了,那麼該方法會去讀取子程序的 exitstatus 或 signalstatus 這兩個域。返回 True 代表子程序好像是在運行,返回 False 表示再也不運行。當平臺是 Solaris 時,可能須要幾秒鐘才能獲得正確的狀態。 當子程序退出後立馬執行 isalive() 有時可能會返回 1 (True),這是一個 race condition,緣由是子程序已經關閉了其文件描述符,可是在 isalive() 執行前尚未徹底的退出。增長一個小小的延時會對 isalive() 的結果有效性有幫助。

清單 17. isalive() 的例子代碼
1
2
3
4
5
6
7
8
9
# 如下程序有時會返回 1 (True)
child = pexpect.spawn('ls')
child.expect(pexpect.EOF)
print child.isalive()
# 可是若是在 isalive() 以前加個小延時,就會一直返回 0 (False)
child = pexpect.spawn('ls')
child.expect(pexpect.EOF)
time.sleep(0.1)    # 以前要 import time,單位是秒 s
print child.isalive()

delaybeforesend 的使用 tips

spawn 類的域 delaybeforesend 能夠幫助克服一些古怪的行爲。好比,經典的是,當一個用戶使用 expect() 期待 "Password:" 提示符時,若是匹配,立馬 sendline() 發送密碼給子程序,可是這個用戶會看到他們的密碼被 echo back 回顯回來了。這是由於,一般許多應用程序都會在打印出 "Password:" 提示符後,立馬關掉 stdin 的 echo,可是若是你發送密碼過快,在程序關掉 stdin 的 echo 以前就發送密碼出去了,那麼該密碼就會被 echo 出來。

清單 18. delaybeforesend 的例子代碼
1
2
3
4
5
6
7
8
9
10
11
child.expect ('[pP]assword:')
child.sendline (my_password)
# 在 expect 以後,某些應用程序,如 SSH,會作以下動做:
#1. SSH 打印 "password:" 提示符給用戶
#2. SSH 關閉 echo.
#3. SSH 等待用戶輸入密碼
# 可是如今第二條語句 sendline 可能會發生在 1 和 2 之間,即在 SSH 關掉 echo 以前輸入了 password 給子程序 , 從
# 而在 stdout,該 password 被 echo 回顯出來,出現了 security 的問題
# 因此此時能夠經過設置 delaybeforesend 來在將數據寫(發送)給子程序以前增長一點點的小延時,由於該問題經
# 常出現,因此默認就 sleep 50ms. 許多 linux 機器必須須要 0.03s 以上的 delay
self.delaybeforesend = 0.05 # 單位秒
 

相關主題

 

參考帖子

http://www.cnblogs.com/dkblog/archive/2013/03/20/2970738.html

相關文章
相關標籤/搜索