Python的Pexpect詳解 [圖片]

Pexpect 是一個用來啟動子程序并對其進行自動控制的純 Python 模塊。 Pexpect 可以用來和像 ssh、ftp、passwd、telnet 等命令行程序進行自動交互。繼第一部分《探索 Pexpect,第 1 部分:剖析 Pexpect 》介紹了 Pexpect 的基礎和如何使用后,本文將結合具體實例入手,詳細介紹 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 的例子代碼

?

#!/usr/bin/env pythonimport 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 = " + indexchild.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)

?

?

注:

?

  • 運行后,輸出結果為:
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 文件中 ./command.py [-a] [-c command] {logfilename} -c 后接的是要運行的命令的名字,默認是“ls -l”; logfilename 是記錄命令運行結果的 log 文件名,默認是“command.log”;指定 -a 表示命令的輸出結果會附加在 logfilename 后,如果 logfilename 之前已經存在的話。

?

?


清單 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.logscript_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 文件的 titlefout.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 0if __name__ == "__main__":try:main()except SystemExit, e:raise eexcept Exception, e:print "ERROR"print str(e)traceback.print_exc()os._exit(1)

?

?

注:

?

  • 運行:./command.py -a -c who cmd.log
運行結束后,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 內容為空的例子代碼

?

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 命令的內容。

?

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


清單 4. 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 thepxssh 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 toconnect to a new host and ssh asks you if you want to accept the public keyfingerprint 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: # Timeoutprint 'ERROR!'print 'SSH could not login. Here is what SSH said:'print child.before, child.afterreturn 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: # Timeoutprint 'ERROR!'print 'SSH could not login. Here is what SSH said:'print child.before, child.afterreturn None# 輸入密碼.child.sendline(password)return childdef 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.EOFchild.expect(pexpect.EOF)# 輸出命令結果.print child.beforeif __name__ == '__main__':try:main()except Exception, e:print str(e)traceback.print_exc()os._exit(1)

?

?

注:

?

  • 運行后,輸出結果為:
Hostname: develperWorks.ibm.com
User: root
Password:
Enter the command: ls -ltotal 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 相關技術,請參閱參考資料)

?

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


清單 5. 使用 pxssh 的例子代碼

?

#!/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')# 匹配 prompts.prompt()# 將 prompt 前所有內容打印出,即命令 'uptime' 的執行結果.print s.before# 發送命令 ' ls -l 's.sendline ('ls -l')# 匹配 prompts.prompt()# 將 prompt 前所有內容打印出,即命令 ' ls -l ' 的執行結果.print s.before# 退出 ssh sessions.logout()
except pxssh.ExceptionPxssh, e:print "pxssh failed on login."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.01ls -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 方法

?

	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方法
prompt (self, timeout=20):

?

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

?

?

  • logout方法

?

logout (self):

?

?

?

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

?

例 5:telnet 的使用

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


清單 6. 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.beforeprint "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)

?

?

  • 運行后,輸出結果為:

?

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方法

?

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 值的例子代碼

?

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 的例子代碼

?

?

# 打開 loggging 功能并將結果輸出到屏幕上
child = pexpect.spawn (foo)
child.logfile = sys.stdout

?

?

pexpect 不會解釋 shell 中的元字符

?

  • pexpect 不會解釋 shell 的元字符,如重定向 redirect,管道 pipe,和通配符 wildcards( “ > ” , “ | ”和“ * ”等 ) 如果想用的話,必須得重新啟動一個新 shell(在 spawn 的參數 command 中是不會解釋他們的,視其為 command string 的一個普通字符)


清單 9. 重新啟動一個 shell 來規避 pexpect 對元字符的不解釋

?

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 構造函數初始化時指定為其它時間,如:

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

?

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


清單 10. 忽略 timeout 超時限制的例子代碼

?

?

child = pexpect.spawn( "telnet develperWorks.ibm.com" )
child.expect( "login", timeout=None )

?

?

  • EOF 異常

?

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

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 的使用的例子代碼

?

# 使用 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() 的例子代碼

?

?

# 在 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() 的例子代碼

?

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() 的最小匹配例子代碼

?

# 如果輸入是 '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

?

child.expect ('\r\n')

?

?

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


清單 16. 匹配一行結束 2

?

?

# 成功在一行結束前匹配一個單詞
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 (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 的例子代碼

?

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 # 單位秒

?

?

?

?

參考資料

?

?

?

  • 訪問 《探索 Pexpect,第 1 部分:剖析 Pexpect 》,了解 pexpect 基礎知識。
    ?
  • 訪問 Expect 的主頁,了解更多 Expect 的內容。
    ?
  • 訪問 Python 官方網頁,了解更多 Python 相關知識。
    ?
  • 有關正則表達式方面相關知識,請訪問正則表達式。
    ?
  • 更多 SSH 相關知識,請訪問 SSH。
    ?
  • 更多 os.system() 的介紹,請訪問 os.system()。
    ?
  • 在 developerWorks Linux 專區 尋找為 Linux 開發人員(包括 Linux 新手入門)準備的更多參考資料,查閱我們 最受歡迎的文章和教程。
    ?
  • 在 developerWorks 上查閱所有 Linux 技巧 和 Linux 教程。

?

?

原文鏈接: http://www.ibm.com/developerwork...

?

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/445211.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/445211.shtml
英文地址,請注明出處:http://en.pswp.cn/news/445211.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

關系數據庫——sql增刪改

數據的插入 插入元祖 --1. 表名后沒有指定屬性列:表示要插入的是一條完整的元組,且屬性列屬性與表定義中的順序一致 insert into student values (201215128, 陳東, 18, 男, IS);--2. 在表明后指定要插入數據的表名及屬性列,屬性列的順序可…

機器學習中的聚類方法總結

聚類定義 定義 聚類就是對大量未知標注 的數據集,按數據 的內在相似性將數據集劃分為多個類別,使 類別內的數據相似度較大而類別間的數據相 似度較小。是無監督的分類方式。 聚類思想 給定一個有N個對象的數據集,構造數據的k 個簇&#x…

學點數學(1)-隨機變量函數變換

隨機變量函數變換本文介紹一維隨機變量函數變換,參考文獻:https://wenku.baidu.com/view/619f74ac3186bceb19e8bbd0.html變換TTT作用于隨機變量XXX,產生隨機變量YYY. T:X?>Y或者寫為yT(x)T:X->Y 或者寫為 yT(x)T:X?>Y或者寫為yT(x…

關系數據庫——關系數據語言

關系 域:一組具有相同數據類型的值的集合(即取值范圍) 笛卡爾積:域上的一種集合運算。結果為一個集合,集合的每一個元素是一個元組,元組的每一個分量來自不同的域。 基數:一個域允許的不同取值…

Python模塊(2)-Numpy 簡易使用教程

Numpy模塊 簡易使用教程1.數組創建2.數組基本屬性-維度、尺寸、數據類型3.數組訪問-索引、切片、迭代4.數組的算術運算-加減乘除、轉置求逆、極大極小5.通用函數-sin,cos,exp,sqrtnp.dot與np.matmul的區別6.數組的合并和分割6.1 np.vstack(),np.hstack()6.2 np.stack()7.list與…

機器學習問題總結(01)

文章目錄1.請描述推薦系統中協同過濾算法CF的原理2.請描述決策樹的原理、過程、終止條件,以及如何防止過擬合2.1決策樹生成算法2.2 剪枝處理(防止過擬合)2.3 停止條件2.4 棵決策樹的生成過程2.5 決策樹的損失函數3.請描述K-means的原理&#…

pthread_attr_init線程屬性

1.線程屬性 線程具有屬性,用pthread_attr_t表示,在對該結構進行處理之前必須進行初始化,在使用后需要對其去除初始化。我們用pthread_attr_init函數對其初始化,用pthread_attr_destroy對其去除初始化。 1. …

Python實例講解 -- 解析xml

Xml代碼 <?xml version"1.0" encoding"utf-8"?> <info> <intro>信息</intro> <list id001> <head>auto_userone</head> <name>Jordy</name> <number&g…

springboot3——Email

maven導入包&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId><version>2.1.6.RELEASE</version></dependency> 參數配置&#xff1a; # MailPrope…

python(22)--面向對象1-封裝

python面向對象1面向過程/面向對象2面向對象核心概念-類3類的設計3.1類三要素-類名、屬性、方法3.2面向對象基礎語法3.2.1查看對象的常用方法3.2.2類定義3.2.3創建類對象3.2.4__init__()方法3.2.5 self參數3.2.6類內置方法和屬性_del_()方法--銷毀對象_str_()方法--定制化輸出對…

機器學習問題總結(02)

文章目錄1.stacking模型以及做模型融合的知識1.1 從提交結果中融合1.2 stacking1.3 blending2. 怎樣去優化SVM算法模型的&#xff1f;2.1 SMO優化算法2.2 libsvm 和 Liblinear3.現有底層是tensorflow的keras框架&#xff0c;如果現在有一個tensorflow訓練好的模型&#xff0c;k…

python對操作系統的目錄和文件操作

一、獲取當前目錄下的特定文件列表>>>import glob,os>>>curdir os.getcwd() #獲取當前目錄>>>os.chdir(workdir) #設置當前目錄>>>dir glob.glob(*.dat) #獲取當前目錄的dat文件列表>>>os.chdir(curdir) #…

常見漏洞

Cookie without HttpOnly flag set 如果在Cookie上設置了HttpOnly屬性&#xff0c;則客戶端JavaScript無法讀取或設置Cookie的值。 這種措施通過阻止某些客戶端攻擊&#xff08;例如跨站點腳本&#xff09;&#xff0c;通過阻止它們通過注入的腳本來簡單地捕獲cookie的值&…

python函數星號參數

2011-09-01 17:35 2人閱讀 評論(0) 收藏 編輯 刪除 今天有個工作是導出一個函數給腳本用 我自已先要測一下 先要客戶端發送一個消息給服務器 看了下C部分的代碼,如下 "def onNetMessage(self,playerID, msgName,msgParam):\n" //客戶端調用服務器腳本 " …

MachineLearning(3)-流型

流型-manifold在很多機器學習的文章中會見到“嵌入在高維空間的低維流型”這樣的字眼&#xff0c;下記錄一些重要概念。參考資料&#xff1a;https://blog.csdn.net/sinat_32043495/article/details/789977581.流型 局部具有歐幾里得空間性質的空間&#xff08;流型就是一個空間…

C/C++常見面試題(四)

C/C面試題集合四 目錄 1、什么是C中的類&#xff1f;如何定義和實例化一個類&#xff1f; 2、請解釋C中的繼承和多態性。 3、什么是虛函數&#xff1f;為什么在基類中使用虛函數&#xff1f; 4、解釋封裝、繼承和多態的概念&#xff0c;并提供相應的代碼示例 5、如何處理內…

機器學習問題總結(03)

文章目錄1.struct和class區別&#xff0c;你更傾向用哪個2.kNN&#xff0c;樸素貝葉斯&#xff0c;SVM的優缺點&#xff0c;各種算法優缺點2.1 KNN算法2.2 樸素貝葉斯2.3SVM算法2.4 ANN算法2.5 DT算法3. 10億個整數&#xff0c;1G內存&#xff0c;O(n)算法&#xff0c;統計只出…

python源代碼現成重用大全

Nullege is a search engine for Python source code. http://nullege.com/

redis——新版復制

sync雖然解決了數據同步問題&#xff0c;但是在數據量比較大情況下&#xff0c;從庫斷線從來依然采用全量復制機制&#xff0c;無論是從數據恢復、寬帶占用來說&#xff0c;sync所帶來的問題還是很多的。于是redis從2.8開始&#xff0c;引入新的命令psync。 psync有兩種模式&a…

Python(23)-面向對象2-繼承,多態

面向對象基本概念2--繼承、多態1.繼承基本概念2.子類重寫父類方法2.1完全重寫2.2擴展父類方法--super()3.多繼承4.新式類和舊式類5.多態基本概念6.類屬性、類方法-classmethod6.1類屬性6.2類方法classmethod7.靜態方法staticmethod8.案例分析本系列博文來自學習《Python基礎視頻…