程式扎記: [ Python 文章收集 ] python Pexpect

標籤

2014年10月3日 星期五

[ Python 文章收集 ] python Pexpect

Source From Here 
Preface 
Pexpect 是一個用來啟動子程序並對其進行自動控制的純Python 模塊。Pexpect 可以用來和像ssh、ftp、passwd、telnet 等命令行程序進行自動交互。繼第一部分《探索Pexpect,第1 部分:剖析Pexpect 》介紹了Pexpect 的基礎和如何使用後,本文將結合具體實例入手,詳細介紹Pexpect 的用法和在實際應用中的注意點。 

Pexpect 是個純Python語言實現的模塊,使用其可以輕鬆方便的實現與 ssh、ftp、passwd 和 telnet 等程序的自動交互,但是讀者的理解還可能只是停留在理論基礎上,本文將從實際例子入手具體介紹 Pexpect 的使用場景和使用心得體驗,實例中的代碼讀者都可以直接拿來使用,相信會對大家產生比較大的幫助。 

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

Note. 
本例 expect 函數中的pattern 使用了 List,並包含了 pexpect.EOF 和 pexpect.TIMEOUT,這樣出現了超時或者 EOF,不會拋出 expection 。(關於expect() 函數的具體使用,請參閱參考資料). 如果程序運行中間出現了錯誤,如用戶名密碼錯誤,超時或者 EOF,遠程 server 連接不上,都會使用 child.close(force=True) 關掉 ftp 子程序。調用 close 可以用來關閉與子程序的 connection 連接,如果你不僅想關閉與子程序的連接,還想確保子程序是真的被 terminate 終止了,設置參數force=True,其最終會調用 child.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. #!/usr/bin/env python  
  2. """  
  3. This run a user specified command and log its result.  
  4.   
  5. ./command.py [-a] [-c command] {logfilename}  
  6.   
  7. logfilename : This is the name of the log file. Default is command.log.  
  8. -a : Append to log file. Default is to overwrite log file.  
  9. -c : spawn command. Default is the command 'ls -l'.  
  10.   
  11. Example:  
  12.   
  13. This will execute the command 'pwd' and append to the log named my_session.log:  
  14.   
  15. ./command.py -a -c 'pwd' my_session.log  
  16.   
  17. """  
  18. import os, sys, getopt  
  19. import traceback  
  20. import pexpect  
  21.   
  22. # 如果程序中間出錯,打印提示信息後退出  
  23. def exit_with_usage():  
  24.     print globals()['__doc__']  
  25.     os._exit(1)  
  26.   
  27. def main():  
  28.     ################################################## ####################  
  29.     # Parse the options, arguments, get ready, etc.  
  30.     ################################################## ####################  
  31.     try:  
  32.         optlist, args = getopt.getopt(sys.argv[1:], 'h?ac:', ['help','h','?'])  
  33.     # 如果指定的參數不是' -a ' , ' -h ' , ' -c ' , ' -? ' , ' --help ' ,  
  34.     #' --h '' --? '時,會拋出exception,  
  35.     # 這裡catch 住,然後打印出exception 的信息,並輸出usage 提示信息.  
  36.     except Exception, e:  
  37.         print str(e)  
  38.         exit_with_usage()  
  39.     options = dict(optlist)  
  40.     # 最多只能指定一個logfile,否則出錯.  
  41.     if len(args) > 1:  
  42.         exit_with_usage()  
  43.     # 如果指定的是'-h','--h','-?','--?' 或'--help',只輸出usage 提示信息.  
  44.     if [elem for elem in options if elem in ['-h','--h','-?','--?','--help']]:  
  45.         print "Help:"  
  46.         exit_with_usage()  
  47.     # 獲取logfile 的名字.  
  48.     if len(args) == 1:  
  49.         script_filename = args[0]  
  50.     else:  
  51.     # 如果用戶沒指定,默認logfile 的名字是command.log  
  52.         script_filename = "command.log"  
  53.     # 如果用戶指定了參數-a,如果之前該logfile 存在,那麼接下來的內容會附加在原先內容之後,  
  54.     # 如果之前沒有該logfile,新建一個文件,並且接下來將內容寫入到該文件中.  
  55.     if '-a' in options:  
  56.         fout = open (script_filename, "ab")  
  57.     else:  
  58.     # 如果用戶沒指定參數-a,默認按照用戶指定logfile 文件名新建一個文件,然後將接下來將內容寫入到該文件中.  
  59.         fout = open (script_filename, "wb")  
  60.     # 如果用戶指定了-c 參數,那麼運行用戶指定的命令.  
  61.     if '-c' in options:  
  62.         command = options['-c']  
  63.     # 如果用戶沒有指定-c 參數,那麼默認運行命令'ls – l'  
  64.     else:  
  65.         command = "ls -l"  
  66.   
  67.     # logfile 文件的title  
  68.     fout.write ('==========Log Tile: IBM developerWorks China==========\n')  
  69.   
  70.     # 為接下來的運行命令生成一個pexpect 的spaw​​n 類子程序的對象.  
  71.     p = pexpect.spawn(command)  
  72.     # 將之前open 的file 對象指定為spawn 類子程序對象的log 文件.  
  73.     p.logfile = fout  
  74.     # 命令運行完後,expect EOF 出現,這時會將spawn 類子程序對象的輸出寫入到log 文件.  
  75.     p.expect(pexpect.EOF)  
  76.     #open 完文件,使用完畢後,需關閉該文件.  
  77.     fout.close()  
  78.     return 0  
  79.   
  80. if __name__ == "__main__":  
  81.     try:  
  82.         main()  
  83.     except SystemExit, e:  
  84.         raise e  
  85.     except Exception, e:  
  86.         print "ERROR"  
  87.         print str(e)  
  88.         traceback.print_exc()  
  89.         os._exit(1)  
運行:./command.py -a -c who cmd.log 運行結束後,cmd.log 的內容為: 
IBM developerWorks China
Root :0 2​​009-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_read 和 logfile_send 
logfile_read:只用來記錄python 主程序接收到child 子程序的輸出,有的時候你不想看到寫給child 的所有東西,只希望看到child 發回來的東西。logfile_send:只用來記錄python 主程序發送給child 子程序的輸入logfile、logfile_read 和logfile_send 何時被寫入呢?logfile、logfile_read 和logfile_send 會在每次寫write 和send 操作後被flush 。

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

Note. 
使用了getpass.getpass() 來獲得用戶輸入的密碼,與 raw_input 不同的是,getpass.getpass() 不會將用戶輸入的密碼字符串echo 回顯到stdout 上。

例4:pxssh 的使用 
本例實現瞭如下功能:使用pexpect 自帶的pxssh 模塊實現ssh 登錄到某個用戶指定的主機上,運行命令' uptime '和' ls -l ',並輸出該命令的結果。 
- 清單5. 使用pxssh 的例子代碼 
  1. #!/usr/bin/env python  
  2. import pxssh  
  3. import getpass  
  4. try:  
  5.     # 調用構造函數,創建一個pxssh 類的對象.  
  6.     s = pxssh.pxssh()  
  7.     # 獲得用戶指定ssh 主機域名.  
  8.     hostname = raw_input('hostname: ')  
  9.     # 獲得用戶指定ssh 主機用戶名.  
  10.     username = raw_input('username: ')  
  11.     # 獲得用戶指定ssh 主機密碼.  
  12.     password = getpass.getpass('password: ')  
  13.     # 利用pxssh 類的login 方法進行ssh 登錄,原始prompt 為'$' , '#''>'  
  14.     s.login (hostname, username, password, original_prompt='[$#>]')  
  15.     # 發送命令'uptime'  
  16.     s.sendline ('uptime')  
  17.     # 匹配prompt  
  18.     s.prompt()  
  19.     # 將prompt 前所有內容打印出,即命令'uptime' 的執行結果.  
  20.     print s.before  
  21.     # 發送命令' ls -l '  
  22.     s.sendline ('ls -l')  
  23.     # 匹配prompt  
  24.     s.prompt()  
  25.     # 將prompt 前所有內容打印出,即命令' ls -l ' 的執行結果.  
  26.     print s.before  
  27.     # 退出ssh session  
  28.     s.logout()  
  29. except pxssh.ExceptionPxssh, e:  
  30.     print "pxssh failed on login."  
  31.     print str(e)  
運行後,輸出結果為: 
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. login (self,server,username,password='',terminal_t​​ype='ansi',   
  2.       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)  
這只是匹配提示符,不能匹配別的string,如果要匹配特殊string,需直接使用父類spawn的expect方法。prompt方法相當於是expect方法的一個快捷方法。如果auto_prompt_reset為False,這時需要手動設置PROMPT域為某個正則表達式來match接下來要出現的prompt,因為prompt()函數默認是expect被重置過的PROMPT的。 

例5:telnet 的使用 
本例實現瞭如下功能:telnet 登錄到某遠程主機上,輸入命令“ls -l”後,將子程序的執行權交還給用戶,用戶可以與生成的telnet 子程序進行交互。 
- 清單6. telnet 的例子代碼 
  1. #!/usr/bin/env python  
  2. import pexpect  
  3.   
  4. # 即將telnet 所要登錄的遠程主機的域名  
  5. ipAddress = 'develperWorks.ibm.com'  
  6. # 登錄用戶名  
  7. loginName = 'root'  
  8. # 用戶名密碼  
  9. loginPassword = 'passw0rd'  
  10. # 提示符,可能是' $ ' , ' # '' > '  
  11. loginprompt = '[$#>]'  
  12.   
  13. # 拼湊telnet 命令  
  14. cmd = 'telnet ' + ipAddress  
  15. # 為telnet 生成spawn 類子程序  
  16. child = pexpect.spawn(cmd)  
  17. # 期待'login'字符串出現,從而接下來可以輸入用戶名  
  18. index = child.expect(["login""(?i)Unknown host", pexpect.EOF, pexpect.TIMEOUT])  
  19. if ( index == 0 ):  
  20.     # 匹配'login'字符串成功,輸入用戶名.  
  21.     child.sendline(loginName)  
  22.     # 期待"[pP]assword" 出現.  
  23.     index = child.expect(["[pP]assword", pexpect.EOF, pexpect.TIMEOUT])  
  24.     # 匹配"[pP]assword" 字符串成功,輸入密碼.  
  25.     child.sendline(loginPassword)  
  26.     # 期待提示符出現.  
  27.     child.expect(loginprompt)  
  28.     if (index == 0):  
  29.         # 匹配提示符成功,輸入執行命令'ls -l'  
  30.         child.sendline('ls -l')  
  31.         # 立馬匹配'ls -l',目的是為了清除剛剛被echo 回顯的命令.  
  32.         child.expect('ls -l')  
  33.         # 期待提示符出現.  
  34.         child.expect(loginprompt)  
  35.         # 將'ls -l' 的命令結果輸出.  
  36.         print child.before  
  37.         print "Script recording started. Type ^] (ASCII 29) to escape from the script   
  38.               shell."  
  39.         # 將telnet 子程序的執行權交給用戶.  
  40.         child.interact()  
  41.         print 'Left interactve mode.'  
  42.     else:  
  43.         # 匹配到了pexpect.EOF 或pexpect.TIMEOUT,表示超時或者EOF,程序打印提示信息並退出.  
  44.         print "telnet login failed, due to TIMEOUT or EOF"  
  45.         child.close(force=True)  
  46. else:  
  47.     # 匹配到了pexpect.EOF 或pexpect.TIMEOUT,表示超時或者EOF,程序打印提示信息並退出.  
  48.     print "telnet login failed, due to TIMEOUT or EOF"  
  49.     child.close(force=True)  
運行後,輸出結果為: 
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.spawn 對象的字符串value 值,將會給debug提供很多有用信息。 
- 清單7. 打印 pexpect.spawn 對象的字符串value 值的例子代碼 
  1. try:  
  2.     i = child.expect ([pattern1, pattern2, pattern3, etc])  
  3. except:  
  4.     print "Exception was thrown"  
  5.     print "debug information:"  
  6.     print str(child)  
將子程序的input和output打log到文件中或者直接打log到屏幕上也非常有用 
- 清單8. 記錄log 的例子代碼 
  1. # 打開loggging 功能並將結果輸出到屏幕上  
  2. child = pexpect.spawn (foo)  
  3. child.logfile = sys.stdout  
Note. 
pexpect 不會解釋shell 中的元字符
pexpect 不會解釋shell 的元字符,如重定向redirect,管道pipe,和通配符wildcards( “ > ” , “ | ”和“ * ”等) 如果想用的話,必須得重新啟動一個新shell(在spawn 的參數command 中是不會解釋他們的,視其為command string 的一個普通字符


- 清單9. 重新啟動一個shell 來規避pexpect 對元字符的不解釋 
  1. child = pexpect.spawn('/bin/bash -c "ls -l | grep LOG > log_list.txt"')  
  2. child.expect(pexpect.EOF)  
如果想在spawn 出來的新子程序中使用重定向redirect,管道pipe,和通配符wildcards( “ > ” , “ | ”和“ * ”等),好像沒有好的方法,只能不使用這些字符,先利用expect 匹配命令提示符,從而在before 中可以拿到之前命令的結果,然​​後在分析before 的內容達到使用重定向redirect, 管道pipe, 和通配符wildcards 的目的. 

如果子程序沒有在指定的時間內生成任何output,那麼expect() 和read() 都會產生TIMEOUT 異常。超時默認是30s,可以在expect() 和spawn 構造函數初始化時指定為其它時間,如: (如果你想讓expect() 和read() 忽略超時限制,即無限期阻塞住直到有output 產生,設置timeout 參數為None。
  1. child.expect('password:', timeout=120) # 等待120s  
- 清單10. 忽略timeout 超時限制的例子代碼 
  1. child = pexpect.spawn( "telnet develperWorks.ibm.com" )  
  2. child.expect( "login", timeout=None )  
可能會有兩種EOF 異常被拋出,但是他們除了顯示的信息不同,其實本質上是相同的。為了實用的目的,不需要區分它們,他們只是給了些關於你的python 程序到底運行在哪個平台上的額外信息,這兩個顯示信息是: 
End Of File (EOF) in read(). Exception style platform.
End Of File (EOF) in read(). Empty string style platform.

有些UNIX 平台,當你讀取一個處於EOF 狀態的文件描述符時,會拋出異常,其他UNIX 平台,卻只會靜靜地返回一個空字符串來表明該文件已經達到了狀態。 pexpect 模塊除了提供 spawn 類以外,還提供了 run() 函數,使用其可以取代一些spawn 的使用,而且更加簡單明了。 
- 清單11. 使用run() 來替代spawn 的使用的例子代碼 
  1. # 使用spawn 的例子  
  2. from pexpect import *  
  3. child = spawn('scp foo myname@host.example.com:.')  
  4. child.expect ('(?i)password')  
  5. child.sendline (mypassword)  
以上功能,相當於以下run 函數: 
  1. from pexpect import *  
  2. run ('scp foo myname@host.example.com:.', events={'(?i)password': mypassword})  

 

- 清單12. 其它一些使用run() 的例子代碼 
  1. # 在local 機器上啟動apache 的daemon  
  2. from pexpect import *  
  3. run ("/usr/local/apache/bin/apachectl start")  
  4. # 使用SVN check in 文件  
  5. from pexpect import *  
  6. run ("svn ci -m 'automatic commit' my_file.py")  
  7. # 運行一個命令並捕獲exit status  
  8. from pexpect import *  
  9. command_output, exitstatus) = run ('ls -l /bin', withexitstatus=1)  
  10. # 運行SSH,並在遠程機器上執行' ls -l ',如果pattern '(?i)password' 被匹配住,密碼'secret'   
  11. # 將會被發送出去  
  12. run ("ssh username@machine.example.com 'ls -l'", events={'(?i)password':'secret\\n'})  
  13. # 啟動mencoder 來rip 一個video,同樣每5s 鐘顯示進度記號  
  14. from pexpect import *  
  15. def print_ticks(d):  
  16.     print d['event_count']  
  17. 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. import pexpect  
  2. child = pexpect.spawn('ls -l')  
  3. child.expect_exact('pexpect.txt')  
  4. print child.after  
expect() 中的正則表達式不是貪婪匹配greedy match,而是最小匹配,即只匹配緩衝區中最早出現的第一個字符串。因為是依次讀取一個字符的 stream 流來判斷是否和正則表達式所表達的模式匹配,所以如果參數 pattern 是個list,而且不止一次匹配,那麼緩衝區中最早出現的第一個匹配的字符串才算數。 

- 清單14. expect() 的最小匹配例子代碼 
  1. # 如果輸入是'foobar'  
  2. index = p.expect (['bar''foo''foobar'])  
  3. #index 返回是1 ('foo') 而不是2 ('foobar'),即使'foobar' 是個更好的匹配。原因是輸入是個流stream,  
  4. # 當收到foo 時,第1 個pattern ('foo') 就被匹配了,不會等到' bar '的出現了,所以返回 1  
正則表達式中,'$'可以匹配一行的結束(具體'$'正則表達式的使用,請參閱參考資料),但是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),見下例: 
  1. # 成功在一行結束前匹配一個單詞  
  2. child.expect ('\w+\r\n')  
  3. # 以下兩種情況都會失敗  
  4. child.expect ('\w+\n')  
  5. child.expect ('\w+$')  
這個問題其實不只是pexpect 會有,如果你在一個stream 流上實施正則表達式匹配時,都會遇到此問題。正則表達式需要預測,stream 流中很難預測,因為生成這個流的進程可能還沒有結束,所以你很難知道是否該進程是暫時性的暫停還是已經徹底結束。 

child.expect ('.+'); 因為是最小匹配,所以只會返回一個字符,而不是一個整個一行(雖然pexpect 設置了re.DOTALL,會匹配一個新行)。 child.expect ('.*' ); 每次匹配都會成功,但是總是沒有字符返回,因為'*' 表明前面的字符可以出現0 次, 在pexpect 中,一般來說,任何'*' 都會盡量少的匹配。 

測試子程序是否還在運行。這個方法是非阻塞的,如果子程序被終止了,那麼該方法會去讀取子程序的exitstatus 或signalstatus 這兩個域。返回True 表明子程序好像是在運行,返回False 表示不再運行。當平台是Solaris 時,可能需要幾秒鐘才能得到正確的狀態。當子程序退出後立馬執行isalive() 有時可能會返回1 (True),這是一個race condition,原因是子程序已經關閉了其文件描述符,但是在isalive() 執行前還沒有完全的退出。增加一個小小的延時會對isalive() 的結果有效性有幫助。 
- 清單17. isalive() 的例子代碼 
  1. # 以下程序有時會返回1 (True)  
  2. child = pexpect.spawn('ls')  
  3. child.expect(pexpect.EOF)  
  4. print child.isalive()  
  5. # 但是如果在isalive() 之前加個小延時,就會一直返回0 (False)  
  6. child = pexpect.spawn('ls')  
  7. child.expect(pexpect.EOF)  
  8. time.sleep(0.1) # 之前要import time,單位是秒 s  
  9. print child.isalive()  
spawn 類的域delaybeforesend 可以幫助克服一些古怪的行為。比如,經典的是,當一個用戶使用expect() 期待"Password:" 提示符時,如果匹配,立馬sendline() 發送密碼給子程序,但是這個用戶會看到他們的密碼被echo back 回顯回來了。這是因為,通常許多應用程序都會在打印出"Password:" 提示符後,立馬關掉stdin 的echo,但是如果你發送密碼過快,在程序關掉stdin 的echo 之前就發送密碼出去了,那麼該密碼就會被echo 出來。 
- 清單18. delaybeforesend 的例子代碼 
  1. child.expect ('[pP]assword:')  
  2. child.sendline (my_password)  
  3. # 在expect 之後,某些應用程序,如SSH,會做如下動作:  
  4. #1. SSH 打印"password:" 提示符給用戶  
  5. #2. SSH 關閉echo.  
  6. #3. SSH 等待用戶輸入密碼  
  7. # 但是現在第二條語句sendline 可能會發生在1 和2 之間,即在SSH 關掉echo 之前輸入了password 給子程序, 從   
  8. # 而在stdout,該password 被echo 回顯出來,出現了security 的問題  
  9. # 所以此時可以通過設置delaybeforesend 來在將數據寫(發送)給子程序之前增加一點點的小延時,因為該問題經   
  10. # 常出現,所以默認就sleep 50ms. 許多linux 機器必須需要0.03s 以上的delay  
  11. self.delaybeforesend = 0.05 # 單位秒  

Supplement 
探索 Pexpect,第 1 部分:剖析 Pexpect 
IBM Linux developerWork 技巧

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!