[u]Expect 學習筆記
[/u]
接觸Expect是迫不得已。系統管理員在工作中經常會遇到這樣的問題,需要實現一個自動交互的工具,這個工具可以自動Telnet或者Ftp到指定的服務器上,成功login之後自動執行一些命令來完成所需的工作。
當然,有很多編程語言可以去解決此類問題,比如用C、Perl、或者Expect。
顯然,儘管C是無所不能的,但是解決此類問題還是比較困難,除非你熟悉Telnet或者Ftp協議。
曾經見過別人用C實現了一個簡單的Telnet客戶端協議的程序,可以在這個程序加入自己的代碼來捕獲服務端的輸出,根據這些輸出來發送適當的指令來進行遠程控制。
使用Perl一樣可以實現這樣的功能,然而,Expect做的更出色,而且除支持Unix/Linux平臺外,它還支持Windows平臺,它就是爲系統管理和軟件測試方面的自動交互類需求而產生的:
Expect是一個免費的編程工具語言,用來實現自動和交互式任務進行通信,而無需人的干預。
Expect的作者Don Libes在1990年開始編寫Expect時對Expect做有如下定義:
Expect是一個用來實現自動交互功能的軟件套件(Expect [is a] software suite for automating interactive tools)。
引用: Expect語言是基於Tcl的, 作爲一種腳本語言,Tcl具有簡單的語法:
cmd arg arg arg
一條Tcl命令由空格分割的單詞組成. 其中, 第一個單詞是命令名稱, 其餘的是命令參數 .
$foo
$符號代表變量的值. 在本例中, 變量名稱是foo.
[cmd arg]
方括號執行了一個嵌套命令. 例如, 如果你想傳遞一個命令的結果作爲另外一個命令的參數, 那麼你使用這個符號 .
"some stuff"
雙引號把詞組標記爲命令的一個參數. "$"符號和方括號在雙引號內仍被解釋 .
{some stuff}
大括號也把詞組標記爲命令的一個參數. 但是, 其他符號在大括號內不被解釋.
反斜線符號() 是用來引用特殊符號. 例如:n 代表換行. 反斜線符號也被用來關閉"$"符號 , 引號,方括號和大括號的特殊含義 .
最好的學習方法就是邊幹邊學,對於已經熟悉一種編程語言的人來說,用另一種新的語言來寫程序解決問題,是很容易的事。所以大概瞭解一下基本語法後,就一邊動手解決問題,一邊查手冊吧。
關於Tcl和Expect的語法,請參考Unix/Linux 平臺任務的自動化相關部分。
引用: 例1:下面是一個telnet到指定的遠程機器上自動執行命令的Expect腳本,該腳本運行時的輸出如下:
# /usr/bin/expect sample_login.exp root 111111
spawn telnet 10.13.32.30 7001
Trying 10.13.32.30...
Connected to 10.13.32.30.
Escape character is '^]'.
accho console login: root
Password:
Last login: Sat Nov 13 17:01:37 on console
Sun Microsystems Inc. SunOS 5.9 May 2004
#
Login Successfully...
# uname -p
sparc
# ifconfig -a
lo0: flags=2001000849<UP,LOOPBACK,RUNNING,MULTICAST,IPv4,VIRTUAL> mtu 8232 index 1
inet 127.0.0.1 netmask ff000000
eri0: flags=1000843<UP,BROADCAST,RUNNING,MULTICAST,IPv4> mtu 1500 index 2
inet 10.13.22.23 netmask ffffff00 broadcast 10.13.22.255
ether 0:3:ba:4e:4a:aa
# exit
accho console login:
Finished...
引用: 下面是該腳本的源代碼:
# vi sample_login.exp:
proc do_console_login {login pass} {
set timeout 5
set done 1
set timeout_case 0
while ($done) {
expect {
"console login:" { send "$loginn" }
"Password:" { send "$passn" }
"#" {
set done 0
send_user "nnLogin Successfully...nn"
}
timeout {
switch -- $timeout_case {
0 { send "n" }
1 {
send_user "Send a return...n"
send "n"
}
2 {
puts stderr "Login time out...n"
exit 1
}
}
incr timeout_case
}
}
}
}
proc do_exec_cmd {} {
set timeout 5
send "n"
expect "#"
send "uname -pn"
expect "#"
send "ifconfig -an"
expect "#"
send "exitn"
expect "login:"
send_user "nnFinished...nn"
}
if {$argc<2} {
puts stderr "Usage: $argv0 login passwaord.n "
exit 1
}
set LOGIN [lindex $argv 0]
set PASS [lindex $argv 1]
spawn telnet 10.13.32.30 7001
do_console_login $LOGIN $PASS
do_exec_cmd
close
exit 0
上面的腳本只是一個示例,實際工作中,只需要重新實現do_exec_cmd函數就可以解決類似問題了。
引用: 在例1中,還可以學習到以下Tcl的語法:
1. 命令行參數
$argc,$argv 0,$argv 1 ... $argv n
if {$argc<2} {
puts stderr "Usage: $argv0 login passwaord.n "
exit 1
}
2. 輸入輸出
puts stderr "Usage: $argv0 login passwaord.n "
3. 嵌套命令
set LOGIN [lindex $argv 0]
set PASS [lindex $argv 1]
4. 命令調用
spawn telnet 10.13.32.30 7001
5. 函數定義和調用
proc do_console_login {login pass} {
..............
}
6. 變量賦值
set done 1
7. 循環
while ($done) {
................
}
8. 條件分支Switch
switch -- $timeout_case {
0 {
...............
}
1 {
...............
}
2 {
...............
}
}
9. 運算
incr timeout_case
此外,還可以看到 Expect的以下命令:
send
expect
send_user
可以通過-d參數調試Expect腳本:
# /usr/bin/expect -d sample_login.exp root 111111
[ 本帖最後由 mocou 於 2005-12-31 10:55 編輯
]
very_99
回覆於:2005-11-02 09:30:18
EXPECT
交互式程序可編程對話,第5版
expect [ -dDinN ] [ -c cmds ] [ -[f|b] ] cmdfile ] [ args ]'F}
簡介
Expect是一種能利用腳本和其它交互式程序進行對話的程序。通過腳本,expect能夠獲知一個程序應該有怎樣的響應和怎樣是正確的響應。它
採用翻譯式語言來控制流結構和高層結構使對話進行下去。而且,還允許用戶在想要控制的時候能夠直接控制程序,然後再將控制交回給腳本。
Expectk是expect和tk的混合體,它可以象expect和tk那樣使用。同樣,expect也能夠被C和C++直接調用(沒有Tcl存在的情況下)。可以參看libexpect(3)。
Expect這個名字來源於廣泛使用的uucp, Kermit和其它的modem控制程序等的send/expect序列的思想,但是它並不象
uucp那樣,expect對環境沒有很特殊的要求,因此可以作爲用戶級的命令和任何程序交互,而且expect實際上可以同時和多個程序交互。
舉例來說,expect可以做這些事情:
使你的計算機可以回撥,這樣你就不必爲你的上網而支付電話費啦。
一遍遍的開始一個遊戲程序(如rogue),直到那個隨機產生的裝備設置達到最好,然後把控制交給你來玩遊戲。
運行fsck,對它的提問按預先設置的標準給出響應“yes”,“no”或者將控制交給你。
連到另外一個網絡或BBS(如MCI Mail, CompuServe),自動收下你的信件,就好象原來發到你的本地系統一樣。
攜帶rlogin, telnet, tip, su, chgrp等需要的環境變量,當前目錄或其它信息
用普通腳本來執行一個任務存在着很多種理由使得是不可行的,(如果你試試就知道了)但用expect就都成爲可能了。
總地說來,expect在運行那些需要在程序和用戶之間進行交互的程序時是很有用的。交互一旦被程序化地指定了,運行起來會很方便。如果需要,Expect也可以將控制交還給用戶(不停止正在運行的程序)。類似的,用戶也可以在任何時間把控制交還給腳本。|
注:老外可真夠羅嗦的,就這麼簡單的意思,讓我翻譯這麼半天,還是我來簡單說說吧?:expect是個腳本解釋程序,就好象/bin/sh,
/bin/ksh一樣。所完成的功能呢,最簡單的就是自動對需要人工交互和程序進行自動交互,比如一個程序需要你不斷地輸入yes繼續,你懶得做,乾脆用
寫個expect腳本自動輸入yes就行了。當然,expect可以做的事情遠不止這些,它實際上是
tcl(Tool Command Language)的一個變種,格式和tcl程序也類似,寫expect腳本對懂tcl的人應該不難。用過
secureCRT的人應該知道有個自動登錄的設置,那就是利用expect實現的。好了,我不羅嗦了,繼續幹活。
引用:用法
expect從cmdfile中讀取命令列表來執行,同樣它也可以在有執行權限的腳本的第一行中加上#!標識來隱式地執行,如:
#!/usr/local/bin/expect -f
當然,路徑應該準確地描述expect的位置,/usr/local/bin只是一個例子。
-c參數指示其後的命令在腳本的最先開始執行,命令應該用引號引起來以不被shell打散。這個選項可被多次使用。多個命令如果用一個-c指示,則應用分號分隔。命令將按其書寫順序執行。(使用expectk時,這個參數用作-command)
-d參數允許一些診斷輸出,報告主要的expect和交互命令行爲。在expect腳本開始用exp_internal 1也可以起到一樣的作
用,-d會多打出expect的版本。(strace命令在跟蹤狀態時很有用,trace命令在跟蹤變量時很有用)(expectk中此參數爲
-diag)。
-D參數打開交互debugger,後跟一個整數。如果這個整數是非零,或者^C被按下(或者碰到一個設置的斷點,或者腳本中設置的其它合適的
debugger命令)Debugger會在下一個tcl過程之前控制程序。關於debugger的信息參看README或
SEE ALSO。(expectk中此參數爲-Debug)
-f參數指定從哪個文件中讀取命令。當被用在#!指示(見上)中時此參數是可選的,所以其它參數可在命令行中提供。(expectk中爲-file)。
-b參數。缺省地,命令文件被整個地讀到內存中執行,但是有時需要一行行地讀取,比如,標準輸入stdin就是這樣。爲了強制特定的文件被這樣讀
入,可以使用-b參數。(expectk中爲-buffer)。如果文件名是“-”,則表示從標準輸入stdin讀入。(用“./-”來表示一個叫作“-
”的文件)
-i參數使expect交互地提示輸入命令,而不是從文件中讀命令。命令提示行通過exit命令或一個eof字符結束。參看interpreter(見下)。-i假設既沒有命令文件,又沒有使用-c參數。(expectk中爲-interactive)。
--用來對選項參數結束的劃界。在你想傳遞一個象選項參數樣的參數給你的腳本時,這個選項是很有用的,它使得expect不對其進行翻譯。也可以放在#!行來阻止expect對任何選項參數格式的參數的翻譯。比如,下面例子將保留原始參數(包括腳本名)到argv中:
#!/usr/local/bin/expect 注意加參數到#!行時應該遵守getopt(3)和execve(2)的慣例。
-N選項。 $exp_library/expect.rc文件如果存在的話將被自動的啓用,除非-N選項被使用。(expectk中爲
-NORC)這樣的話就會自動找~/.expect.rc,除非加了-n參數。如果定義了環境變量DOTDIR,那就會從那裏
找.expect.rc。(expectk中爲-norc)。expect.rc的使用只在執行完-c參數指定的命令後。
-v打印expect的版本號並退出。(expectk中爲-version)
可選的args被結構化成一個列表存在argv中,argc被初始化成argv的長度。
Argv0被定義爲腳本的名字。下面例子打印出腳本名和前三個參數:
send_user "$argv0 [lrange $argv 0 2]
[ 本帖最後由 mocou 於 2005-12-31 10:57 編輯
]
very_99
回覆於:2005-11-02 09:32:48
% set i 1
1
字符串應該用引號括起來:
% set str "test"
'test'
要輸出一個標量的內容,使用put語句:
% puts $str
test
$用來說明str是一個變量。puts函數在標準輸出顯示變量的內容。
數組也可以用set語句定義,實際上,tcl中建立數組只是單個建立數組的元素。例如
,
% set arr(1) 0
0
% set arr(2) 1
1
這樣就建立了一個兩個元素的數組arr。在TCL中,不存在相當於數組邊界這樣的東西
,例如
% set arr(100) to
to
這時數組中實際只存在arr(1),arr(2)和arr(100),這是和C語言不同的地方。用arr
ay size命令可以返回數組的大小:
% array size arr
3
訪問數組的方法和訪問標兩實際是一樣的,例如:
% puts $arr(100)
to
可以用同樣的方法創建多維數組。
要使用數組中的所有元素,需要使用一種特殊的便利方式。首先要啓動startsearsh:
% array startsearch arr
s-1-arr
這裏返回了一個搜索id,你可以把它傳遞給某個變量,因爲以後還要使用它進行進一
步的搜索:
% set my_id [array startsearch arr]
s-1-arr
現在my_id的內容是s-1-arr,然後,就可以搜索arr的內容了:
% array nextelement arr $my_id
whi
這裏的array nextelement返回的是什麼?可能有點出乎你的意料,是arr數組的下標
,再執行一次array nextelement命令又會找出另外一個下標:
% array nextelement arr $my_id
4
這樣遍歷下去,可以找出arr數組的所有下標,而知道下標之後,就可以用$arr(4)之
類的方式訪問arr的內容了。當遍歷完成之後,array nextelement命令將簡單地返回:
% array nextelement arr $my_id
%
這時就可以停止遍歷過程了,如果你想確認遍歷是否完成,可以使用array anymore命
令:
% array anymore arr $my_id
0
返回0說明遍歷已經完成。
串處理
TCL中可以進行一般的串處理過程,這可以使用string命令和append命令,append命令
將某個字符串加到另外一個字符串的後面:
% set str1 "test "
test
% set str2 "cook it"
cook it
% append str1 $str2 " and other"
test cook it and other
string命令可以執行字符串的比較,刪除和查詢,其格式是 string [參數] string1
[string2]
參數可以是下面的命令之一:
compare 按照字典順序對字符串進行比較,根據相對關係返回-1,0或者+1。
first 返回string2中第一次出現string1的位置,如果失敗,返回-1。
last 返回string2中最後一次出現string1的位置,如果失敗,返回-1
trim 從string1中刪除開頭和結尾的出現在string2中的字符
trimleft 從string1中刪除開頭的出現在string2中的字符。
trimright 從string1中刪除結尾的出現在string2中的字符
下面幾個用在string中的參數不需要string2變量:
length 返回tring1的長度
tolower 返回將string1全部小寫化的串
toupper 返回將string1全部大寫化的串
運算
TCL的運算方式比較彆扭,它使用expr命令作爲計算符號,其用法類似C語言的+=和/=
,例如,
% set j [expr $i/5]
1
注意TCL會自動選擇整數或者浮點計算:
% set l [ expr $i /4.0]
1.25
% set l [ expr $i /4]
1
在TCL裏面可以使用+ - * /和%作爲基本運算符,另外通常還包括一些數學函數,如a
bs,sin,cos,exp和power(乘方)等等。
另外,還有一個起運算符作用的命令incr,它用來對變量加一:
% set i 1
1
% incr i
2
流程控制
tcl支持分支和循環。分支語句可以使用if和switch實現。if語句的和C語言類似,如
if { $ x < 0 } {
set y 10;
}
注意判斷子句也需要使用花括號。
與C語言一樣,tcl的if語句也可以使用else和elseif。
switch語句的用法有點類似這樣:
switch $x {
0 { set y 10;}
10 { set y 100;}
20 { set y 400;}
}
與C的switch語句不同,每次只有符合分支值的子句才被執行。
循環命令主要由for,foreach和while構成,而且每一個都可以使用break和continue
子句。
for語句的格式有點類似這樣:
for { set i 0} {$i < 10} { incr i} {puts $i}
將會輸出從1到9的整數。
如果用while循環,這個句子可以寫成
while {$i < 10 } {
puts $i;
incr i;
}
foreach是對於集合中的每一個元素執行一次命令,大致的命令格式是
foreach [變量] { 集合 } {
語句;
}
例如
% foreach j { 1 3 5} {
put $j;
}
1
3
5
函數
如同在一般的編程語言裏面一樣,在tcl裏面也可以定義函數,這是通過proc命令實現
的:
proc my_proc {i}{
puts $i;
}
這樣就定義了一個名字叫proc的函數,它只是在終端顯示輸入變元的內容。
要使用這個函數,簡單地輸入它的名字:
% my_proc { 5 }
5
如果變元的數目是0,只要使用空的變元列表,例如 proc my_proc {} {語句;}
儘管tcl還可以處理更復雜的過程,但是我們不再介紹了,例如文件的讀寫以及tk圖形
語言,因爲我們處理tcl的主要目標就是理解expect,對於更復雜的編程工作,我們建議
你使用perl。
11.1.2 expect
expect是建立在tcl基礎上的一個工具,它用來讓一些需要交互的任務自動化地完成。
我們首先從一個簡單的例子開始,如同在這一節一開始就提到的,我們想設置一個自動
的文件下載程序。
我們看一看這樣的一個例子腳本:
#! /usr/bin/expect
spawn ftp 202.199.248.11
expect "Name"
send "ftpr"
expect "Password:"
send "nothingr"
expect "apply"
send "cd /pub/UNIX/Linux/remoteXr"
expect "successful."
send "binr"
expect "set to I"
send "get exceed5.zipr"
expect "complete."
send "quitr"
這個是什麼意思?呵呵,就是個自動下載程序。第一行說明這個程序應該調用/usr/b
in/expect去執行,然後的就是expect命令。
察看expect的手冊頁面(man expect)可以得到一個很長的expect說明,可惜其中關於
expect的語法仍然介紹的不夠。一般來說,expect主要用在需要自動執行人機交互的過
程中,例如fsck程序,這個程序會不斷地提問"yes/no",像這樣的命令就可以用expect
來完成。
spawn語句在expect腳本中用於啓動一個新的進程,在我們的程序中,spawn ftp 202
.199.248.11就是去執行ftp程序,接下來,就是expect和send的指令對了。
每一對expect和send指令代表一個信息/迴應。如果這樣說不好理解的話,那麼可以看
一看ftp的具體執行過程:
ftp 202.199.248.11
Connected to 202.199.248.11.
220 mail.asnc.edu.cn FTP server (BeroFTPD 1.3.3(3) Sun Feb 20 15:52:49 CST
2000.
Name (202.199.248.11:wanghy):
顯然,一旦連接成功,服務器會返回一個Name(202.199.248.11:wanghy):的字符串來
要求客戶給出用戶名。expect語句簡單地在返回信息中查詢你給出的字符串,一旦成功
就執行下面的命令,現在,expect " Name"已經成功地找到了Name字符串,接下來可以
執行send命令了。
send命令比expect命令更簡單,它簡單地向標準輸入提交你設定的字符串,現在設置
爲send "ftpr"表示等到登錄信息之後就給出一個輸入ftp回車,也就是標準的登錄過
程。
下面的行與這些行完全一樣,只是機械地等待服務器的迴應,並且提交自己的輸入。
要使用這個expect腳本,你只需要將它設置爲可執行的屬性,然後執行它,expect就
會執行你需要的服務。
由於expect是tcl的擴展,所以你在expect文件中可以象tcl腳本一樣設置變量和程序
流程。
現在我們看一看我們還能夠如何改進我們的expect腳本。ftp命令可能會失敗,比如遠
端的機器可能會無法提供服務,或者在啓動ftp命令時本地機器發生問題。爲了處理這一
類的問題,我們可以使用expect的timeout選項來設置超時的話expect腳本自動退出:
#! /usr/bin/expect
spawn ftp 202.199.248.11
expect {
timeout exit
Connect
}
………………
注意這裏面使用的花括號。它的含義是使用一組並列表達式。使用並列表達式的主要
原因是這樣:如果使用下面的指令對:
expect timeout
exit
那麼由於expect腳本是順序執行的,那麼當程序執行到這個expect的時候就會阻塞,
所以程序會一直等待到timeout然後退出。並列表達式則是相當於switch的行爲,只要列
出的幾項內容有一項得到滿足,expect命令就得到滿足,於是程序可以正常執行。上面
的腳本表示,如果連接ftp的時候發生了超時,那麼就退出,否則,一旦發現Connect應
答,說明服務器已經正常了,那麼就可以繼續運行了。
我們可以看看用tcl能夠對我們的expect腳本提供什麼幫助。我們可以設置讓expect腳
本不斷地連接遠端服務器的服務,直到正常建立連接開始,爲此,我們可以把建立連接
的命令放在一個循環裏面,並且根據迴應的不同自動選擇重新輸入命令還是繼續執行:
spawn ftp
while {1} {
expect "ftp>"
send "o 202.199.248.11r"
expect {
"Connected" break
"refused" { sleep 10} ;
}
}
這裏使用了我們在tcl語言中講到的while和break命令,熟悉C的讀者應該很容易看出
它的行爲:不斷地等待ftp>提示符,在提示符下面發送連接遠端服務器的命令,如果服
務器迴應是refused(連接失敗),就等待10秒鐘,然後開始下一次循環;如果是Conne
cted,那麼就跳出循環執行下面的命令。sleep是expect的一個標準命令,表示暫停若干
秒鐘。
expect還支持許多更復雜的進程控制方式,如fork,disconnect等等,你可以從手冊
頁面中得到詳細的信息。另外,各種tcl運算符和流程控制命令,包括tcl函數也可以使
用。
有些讀者可能會問,如果expect執行的話是否控制檯輸入不能使用了,答案是否定的
。expect命令運行時,如果某個等待的信息沒有得到,那麼程序會阻塞在相應的expect
語句處,這時,你在鍵盤上輸入的東西仍然可以正常地傳遞到程序中去,其實對於那些
expect處理的信息,原則上你輸入的內容仍然有效,只是expect的反映太快,總是搶在
你的前面“輸入”就是了。知道了這一點之後,你就可能寫一個expect腳本,讓expect
自動處理來自fscki的那些噁心的yes/no選項(我們介紹過,這些yes/no其實完全是多餘
的,正常情況下你除了選擇yes之外什麼也幹不了)。
缺省下,expect在標準輸出(你的終端上)輸出所有來自應用程序的迴應信息,你可
以用下面的兩個命令重定向這些信息:
log_file [文件名]
這個命令讓expect在你設置的文件中記錄輸出信息。必須注意,這個選項並不影響控
制臺輸出信息,不過如果你通過crond設置expect腳本在半夜運行的話,你就確實可能需
要這個命令來記錄各種信息了。例如:
log_file expect.log
log_user 0/1
這個選項設置是否顯示輸出信息,設置爲1時是缺省值,爲0 的話,expect將不產生任
何輸出信息,或者說簡單地過濾掉控制檯輸出。必須記住,如果你用log_user 0關閉了
控制檯輸出,那麼你同時也就關閉了對記錄文件的輸出。
這一點很讓人困擾,如果你確實想要記錄expect的輸出卻不想讓它在控制檯上製造垃
圾的話,你可以簡單地把expect的輸出重定向到/dev/null:
./test.exp > /dev/null
你可以象下面這樣使用一對fork和disconnect命令。expect的disconnect命令將使得
相應的進程到後臺執行,輸入和輸出被重定向到/dev/null:
if [fork]!=0 exit
disconnect
fork命令會產生出一個子進程,而且它產生返回值,如果返回的是0,說明這是一個子
進程,如果不爲0,那麼是父進程。因此,執行了fork命令之後,父進程死亡而子進程被
disconnect命令放到後臺執行。注意disconnect命令只能對子進程使用。 |
|