概述 |
例 1:ftp 的使用
本例實現了如下功能:ftp 登錄到 develperWorks.ibm.com 主機上,並用二進制傳輸模式下載一個名叫 rmall的文件。
#!/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)
注: 本例 expect 函數中的 pattern 使用了 List,並包含了 pexpect.EOF和pexpect.TIMEOUT,這樣出現了超時或者 EOF,不會拋出 expection 。(關於 expect() 函數的具體使用,請參閱參考資料) |
例 2:記錄 log |

#!/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)
注: logfile:
運行該腳本后,你會發現其實 log.txt 是空的,沒有記錄 ls -l 命令的內容,原因是沒有調用 send 或 read_nonblocking,真正的內容沒有被 flush 到 log 中。如果在 fout.close() 之前加上 p.expect(pexpect.EOF),log.txt 才會有 ls -l 命令的內容。 |
本例實現了如下功能:ssh 登錄到某個用戶指定的主機上,運行某個用戶指定的命令,並輸出該命令的結果。 |

#!/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)
注:
|
本例實現了如下功能:使用 pexpect 自帶的 pxssh 模塊實現 ssh 登錄到某個用戶指定的主機上,運行命令’ uptime ’和’ ls -l ’,並輸出該命令的結果。 |

#!/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)
使用原始 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 的。
匹配新提示符(不是 original_prompt)。注:這只是匹配提示符,不能匹配別的 string,如果要匹配特殊 string,需直接使用父類 spawn 的 expect 方法。 prompt 方法相當於是 expect 方法的一個快捷方法。如果auto_prompt_reset 為 False,這時需要手動設置 PROMPT 域為某個正則表達式來 match 接下來要出現的 prompt,因為 prompt() 函數默認是 expect 被重置過的 PROMPT 的。 logout 方法
|
本例實現了如下功能:telnet 登錄到某遠程主機上,輸入命令“ls -l”后,將子程序的執行權交還給用戶,用戶可以與生成的 telnet 子程序進行交互。
|

#!/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)
通常一個 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() 模式。 |
清單 7. 打印 pexpect.spawn 對象的字符串 value 值的例子代碼
清單 8. 記錄 log 的例子代碼
清單 9. 重新啟動一個 shell 來規避 pexpect 對元字符的不解釋
如果想在 spawn 出來的新子程序中使用重定向 redirect,管道 pipe,和通配符 wildcards( “ > ” , “ | ”和“ * ”等 ),好像沒有好的方法,只能不使用這些字符,先利用 expect 匹配命令提示符,從而在 before 中可以拿到之前命令的結果,然后在分析 before 的內容達到使用重定向 redirect, 管道 pipe, 和通配符 wildcards 的目的。
如果子程序沒有在指定的時間內生成任何 output,那么 expect() 和 read() 都會產生 TIMEOUT 異常。超時默認是 30s,可以在 expect() 和 spawn 構造函數初始化時指定為其它時間,如:
如果你想讓 expect() 和 read() 忽略超時限制,即無限期阻塞住直到有 output 產生,設置 timeout 參數為 None。 清單 10. 忽略 timeout 超時限制的例子代碼
可能會有兩種 EOF 異常被拋出,但是他們除了顯示的信息不同,其實本質上是相同的。為了實用的目的,不需要區分它們,他們只是給了些關於你的 python 程序到底運行在哪個平台上的額外信息,這兩個顯示信息是:
有些 UNIX 平台,當你讀取一個處於 EOF 狀態的文件描述符時,會拋出異常,其他 UNIX 平台,卻只會靜靜地返回一個空字符串來表明該文件已經達到了狀態。 pexpect 模塊除了提供 spawn 類以外,還提供了 run() 函數,使用其可以取代一些 spawn 的使用,而且更加簡單明了。 清單 11. 使用 run() 來替代 spawn 的使用的例子代碼
清單 12. 其它一些使用 run() 的例子代碼
expect_exact(self, pattern_list, timeout = -1, searchwindowsize = -1); expect_exact() 與 expect() 類似,但是 pattern_list 只能是字符串或者是一個字符串的 list,不能是正則表達式,其匹配速度會快於 expect(),原因有兩個:一是字符串的 search 比正則表達式的匹配要快,另一個則是可以限制只從輸入緩沖的結尾來尋找匹配的字符串。還有當你覺得每次要 escape 正則表達式中的特殊字符為普通字符時很煩,那么你也可以使用 expect_exact() 來取代 expect()。 清單 13. expect_exact() 的例子代碼
expect() 中的正則表達式不是貪婪匹配 greedy match,而是最小匹配,即只匹配緩沖區中最早出現的第一個字符串。因為是依次讀取一個字符的 stream 流來判斷是否和正則表達式所表達的模式匹配,所以如果參數 pattern 是個 list,而且不止一次匹配,那么緩沖區中最早出現的第一個匹配的字符串才算數。 清單 14. expect() 的最小匹配例子代碼
正則表達式中,'$'可以匹配一行的結束(具體'$'正則表達式的使用,請參閱參考資料),但是 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
如果你只是想跳過一個新行,直接 expect('\n') 就可以了,但是如果你想在一行的結束匹配一個具體的 pattern 時,就必須精確的尋找 (\r),見下例: 清單 16. 匹配一行結束 2
這個問題其實不只是 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() 的例子代碼
spawn 類的域 delaybeforesend 可以幫助克服一些古怪的行為。比如,經典的是,當一個用戶使用 expect() 期待 "Password:" 提示符時,如果匹配,立馬 sendline() 發送密碼給子程序,但是這個用戶會看到他們的密碼被 echo back 回顯回來了。這是因為,通常許多應用程序都會在打印出 "Password:" 提示符后,立馬關掉 stdin 的 echo,但是如果你發送密碼過快,在程序關掉 stdin 的 echo 之前就發送密碼出去了,那么該密碼就會被 echo 出來。 清單 18. delaybeforesend 的例子代碼
|