概述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)
|
因爲其依賴 pty module ,因此 Pexpect 還不能在 Windows 的標準 python 環境中執行,若是想在 Windows 平臺使用,可使用在 Windows 中運行 Cygwin 作爲替代方案。linux
根據 Wiki 對 MIT License 的介紹「該模塊被受權人有權利使用、複製、修改、合併、出版發行、散佈、再受權及販售軟件及軟件的副本。被受權人可根據程序的須要修改受權條款爲適當的內容。在軟件和軟件的全部副本中都必須包含版權聲明和許可聲明。」正則表達式
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
1
2
|
from pexpect import *
run ("svn ci -m 'automatic commit' my_file.py")
|
與 os.system() 不一樣的是,使用 run() 能夠方便地同時得到命令的輸出結果與命令的退出狀態 。windows
1
2
|
from pexpect import *
(command_output, exitstatus) = run ('ls -l /bin', withexitstatus=1)
|
command_out 中保存的就是 /bin 目錄下的內容。bash
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 參數的內容。服務器
能夠這樣使用:
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目錄內容
|
當子程序須要參數時,還可使用一個參數的列表:
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 產生的日誌的記錄位置。
例如:
1
2
3
|
child = pexpect.spawn('some_command')
fout = file('mylog.txt','w')
child.logfile = fout
|
還能夠將日誌指向標準輸出:
1
2
|
child = pexpect.spawn('some_command')
child.logfile = sys.stdout
|
若是不須要記錄向子程序輸入的日誌,只記錄子程序的輸出,可使用:
1
2
|
child = pexpect.spawn('some_command')
child.logfile_send = sys.stdout
|
爲了控制子程序,等待子程序產生特定輸出,作出特定的響應,可使用 expect 方法。
1
|
expect(self, pattern, timeout=-1, searchwindowsize=None)
|
在參數中: pattern 能夠是正則表達式, pexpect.EOF , pexpect.TIMEOUT ,或者由這些元素組成的列表。須要注意的是,當 pattern 的類型是一個列表時,且子程序輸出結果中不止一個被匹配成功,則匹配返回的結果是緩衝區中最早出現的那個元素,或者是列表中最左邊的元素。使用 timeout 能夠指定等待結果的超時時間 ,該時間以秒爲單位。當超過預訂時間時, expect 匹配到pexpect.TIMEOUT。
若是難以估算程序運行的時間,可使用循環使其屢次等待直至等待運行結束:
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 表明在等待目標正則表達式中出現了超時。
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等待的目標列表中:
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 成員保存的是緩衝區中與目標正則表達式相匹配的內容。
1
2
3
|
child = pexpect.spawn('/bin/ls /')
child.expect (pexpect.EOF)
print child.before
|
此時 child.before 保存的就是在根目錄下執行 ls 命令的結果。
1
2
3
|
send(self, s)
sendline(self, s='')
sendcontrol(self, char)
|
這些方法用來向子程序發送命令,模擬輸入命令的行爲。 與 send() 不一樣的是 sendline() 會額外輸入一個回車符 ,更加適合用來模擬對子程序進行輸入命令的操做。 當須要模擬發送 「Ctrl+c」 的行爲時,還可使用 sendcontrol() 發送控制字符。
1
|
child.sendcontrol('c')
|
因爲 send() 系列函數向子程序發送的命令會在終端顯示,因此也會在子程序的輸入緩衝區中出現,所以不建議使用 expect 匹配最近一次 sendline() 中包含的字符。不然可能會在形成不但願的匹配結果。
1
|
interact(self, escape_character = chr(29), input_filter = None, output_filter = None)
|
Pexpect還能夠調用interact() 讓出控制權,用戶能夠繼續當前的會話控制子程序。用戶能夠敲入特定的退出字符跳出,其默認值爲「^]」 。
下面展現一個使用Pexpect和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 對象。
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 作爲 pexpect 的派生類能夠用來創建一個 ssh 鏈接,它相比其基類增長了以下方法:
login() 創建到目標機器的ssh鏈接 ;
losuckgout() 釋放該鏈接 ;
prompt() 等待提示符,一般用於等待命令執行結束。
下面的示例鏈接到一個遠程服務器,執行命令並打印命令執行結果。
該程序首先接受用戶輸入用戶名和密碼,login 函數返回一個 pxssh 對象的連接,而後調用 sendline() 分別輸入 「uptime」、「ls」 等命令並打印命令輸出結果。
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)
|
在使用spawn執行命令時應該注意,Pexpect 並不與 shell 的元字符例如重定向符號 > 、>> 、管道 | ,還有通配符 * 等作交互,因此當想運行一個帶有管道的命令時必須另外啓動一個 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 中都不能正常運行。
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 在線程中啓動控制子程序
在實際系統管理員的任務中,有時須要同時管理多臺機器,這個示例程序被用來自動編譯並安裝新的內核版本,並重啓。它使用多線程,每一個線程都創建一個到遠程機器的 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
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,第 1 部分:剖析 Pexpect 》(請參閱參考資料)的介紹,相信你們已經對 Pexpect 的用法已經有了比較全面的瞭解,知道 Pexpect 是個純 Python 語言實現的模塊,使用其能夠輕鬆方便的實現與 ssh、ftp、passwd 和 telnet 等程序的自動交互,可是讀者的理解還可能只是停留在理論基礎上,本文將從實際例子入手具體介紹 Pexpect 的使用場景和使用心得體驗,實例中的代碼讀者均可以直接拿來使用,相信會對你們產生比較大的幫助。 如下是本文所要介紹的全部 Pexpect 例子標題:
例 1:ftp 的使用(注:spawn、expect 和 sendline 的使用)
例 2:記錄 log(注:logfile、
logfile_send
和
logfile_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
本例實現了以下功能:ftp 登陸到 develperWorks.ibm.com 主機上,並用二進制傳輸模式下載一個名叫 rmall
的文件。
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
|
本例實現了以下功能:運行一個命令,並將該命令的運行輸出結果記錄到 log 文件中 ./command.py [-a] [-c command] {logfilename} -c 後接的是要運行的命令的名字,默認是「ls -l」; logfilename 是記錄命令運行結果的 log 文件名,默認是「command.log」;指定 -a 表示命令的輸出結果會附加在 logfilename 後,若是 logfilename 以前已經存在的話。
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_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 。
調用 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,但其最終結果是:內容爲空。見下例:
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 命令的內容。
本例實現了以下功能: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
|
本例實現了以下功能:使用 pexpect 自帶的 pxssh 模塊實現 ssh 登陸到某個用戶指定的主機上,運行命令’ uptime ’和’ ls -l ’,並輸出該命令的結果。
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
|
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'兩次。
本例實現了以下功能:telnet 登陸到某遠程主機上,輸入命令「ls -l」後,將子程序的執行權交還給用戶,用戶能夠與生成的 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.spawn 對象的
字符串 value
值,將會給 debug 提供不少有用信息。
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 到
屏幕上
也很是有用
1
2
3
|
# 打開 loggging 功能並將結果輸出到屏幕上
child = pexpect.spawn (foo)
child.logfile = sys.stdout
|
pexpect 不會解釋 shell 的元字符,如重定向 redirect,管道 pipe,和通配符 wildcards( 「 > 」 , 「 | 」和「 * 」等 ) 若是想用的話,必須得從新啓動一個新 shell(在 spawn 的參數 command 中是不會解釋他們的,視其爲 command string 的一個普通字符)
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 的目的。
若是子程序沒有在指定的時間內生成任何 output,那麼 expect() 和 read() 都會產生 TIMEOUT 異常。超時默認是 30s,能夠在 expect() 和 spawn 構造函數初始化時指定爲其它時間,如:
1
|
child.expect('password:', timeout=120) # 等待 120s
|
若是你想讓 expect() 和 read() 忽略超時限制,即無限期阻塞住直到有 output 產生,設置 timeout 參數爲 None。
1
2
|
child = pexpect.spawn( "telnet develperWorks.ibm.com" )
child.expect( "login", timeout=None )
|
可能會有兩種 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 平臺,卻只會靜靜地返回一個空字符串來代表該文件已經達到了狀態。
pexpect 模塊除了提供 spawn 類之外,還提供了 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})
|
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(self, pattern_list, timeout = -1, searchwindowsize = -1); expect_exact() 與 expect() 相似,可是 pattern_list 只能是字符串或者是一個字符串的 list,不能是正則表達式,其匹配速度會快於 expect(),緣由有兩個:一是字符串的 search 比正則表達式的匹配要快,另外一個則是能夠限制只從輸入緩衝的結尾來尋找匹配的字符串。還有當你以爲每次要 escape 正則表達式中的特殊字符爲普通字符時很煩,那麼你也可使用 expect_exact() 來取代 expect()。
1
2
3
4
|
import pexpect
child = pexpect.spawn('ls -l')
child.expect_exact('pexpect.txt')
print child.after
|
expect() 中的正則表達式不是貪婪匹配 greedy match,而是最小匹配,即只匹配緩衝區中最先出現的第一個字符串。 由於是依次讀取一個字符的 stream 流來判斷是否和正則表達式所表達的模式匹配,因此若是參數 pattern 是個 list,並且不止一次匹配,那麼緩衝區中最先出現的第一個匹配的字符串纔算數。
1
2
3
4
|
# 若是輸入是 'foobar'
index = p.expect (['bar', 'foo', 'foobar'])
#index 返回是 1 ('foo') 而不是 2 ('foobar'),即便 'foobar' 是個更好的匹配。緣由是輸入是個流 stream,
# 當收到 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) 。
1
|
child.expect ('\r\n')
|
若是你只是想跳過一個新行,直接 expect('\n') 就能夠了,可是若是你想在一行的結束匹配一個具體的 pattern 時,就必須精確的尋找 (\r),見下例:
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(self)
測試子程序是否還在運行。這個方法是非阻塞的,若是子程序被終止了,那麼該方法會去讀取子程序的 exitstatus 或 signalstatus 這兩個域。返回 True 代表子程序好像是在運行,返回 False 表示再也不運行。當平臺是 Solaris 時,可能須要幾秒鐘才能獲得正確的狀態。 當子程序退出後立馬執行 isalive() 有時可能會返回 1 (True),這是一個 race condition,緣由是子程序已經關閉了其文件描述符,可是在 isalive() 執行前尚未徹底的退出。增長一個小小的延時會對 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()
|
spawn 類的域 delaybeforesend 能夠幫助克服一些古怪的行爲。好比,經典的是,當一個用戶使用 expect() 期待 "Password:" 提示符時,若是匹配,立馬 sendline() 發送密碼給子程序,可是這個用戶會看到他們的密碼被 echo back 回顯回來了。這是由於,一般許多應用程序都會在打印出 "Password:" 提示符後,立馬關掉 stdin 的 echo,可是若是你發送密碼過快,在程序關掉 stdin 的 echo 以前就發送密碼出去了,那麼該密碼就會被 echo 出來。
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