Expect 學習筆記

http://www.chinaunix.net 作者:very_99   發表於:2006-12-11 15:06:11


[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 



字符串應該用引號括起來: 

% set str "test" 

'test' 

要輸出一個標量的內容,使用put語句: 

% puts $str 

test 

$用來說明str是一個變量。puts函數在標準輸出顯示變量的內容。 

數組也可以用set語句定義,實際上,tcl中建立數組只是單個建立數組的元素。例如 
, 

% set arr(1) 0 



% set arr(2) 1 



這樣就建立了一個兩個元素的數組arr。在TCL中,不存在相當於數組邊界這樣的東西 
,例如 

% set arr(100) to 

to 

這時數組中實際只存在arr(1),arr(2)和arr(100),這是和C語言不同的地方。用arr 
ay size命令可以返回數組的大小: 

% array size arr 



訪問數組的方法和訪問標兩實際是一樣的,例如: 

% 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 



這樣遍歷下去,可以找出arr數組的所有下標,而知道下標之後,就可以用$arr(4)之 
類的方式訪問arr的內容了。當遍歷完成之後,array nextelement命令將簡單地返回: 

% array nextelement arr $my_id 



這時就可以停止遍歷過程了,如果你想確認遍歷是否完成,可以使用array anymore命 
令: 

% array anymore arr $my_id 



返回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] 



注意TCL會自動選擇整數或者浮點計算: 

% set l [ expr $i /4.0] 

1.25 

% set l [ expr $i /4] 



在TCL裏面可以使用+ - * /和%作爲基本運算符,另外通常還包括一些數學函數,如a 
bs,sin,cos,exp和power(乘方)等等。 

另外,還有一個起運算符作用的命令incr,它用來對變量加一: 

% set i 1 



% incr i 



流程控制 

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; 









函數 

如同在一般的編程語言裏面一樣,在tcl裏面也可以定義函數,這是通過proc命令實現 
的: 

proc my_proc {i}{ 

puts $i; 



這樣就定義了一個名字叫proc的函數,它只是在終端顯示輸入變元的內容。 

要使用這個函數,簡單地輸入它的名字: 

% my_proc { 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命令只能對子進程使用。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章