天天看點

expect學習筆記-1

        Expect是一個免費的程式設計工具語言,用來實作自動和互動式任務進行通信,而無需人的幹預。Expect的作者Don Libes在1990年開始編寫Expect時對Expect做有如下定義:Expect是一個用來實作自動互動功能的軟體套件(Expect is a software suite for automating interactive tools)。使用它系統管理者的可以建立腳本用來實作對指令或程式提供輸入,而這些指令和程式是期望從終端(terminal)得到輸入,一般來說這些輸入都需要手工輸入進行的。Expect則可以根據程式的提示模拟标準輸入提供給程式需要的輸入來實作互動程式執行。甚至可以實作實作簡單的BBS聊天機器人。

        在互動式的場景中,當你輸入指令後,可能伺服器端會傳回一些操作提示符,以讓你輸入指令。Expect提供了這樣三個常用的指令,spawn, expect和send,恰好滿足這種需要。Spawn指令激活一個Unix程式來進行互動式的運作。send指令向程序發送字元串。expect指令等待程序的某些字元串。expect支援正規表達式并能同時等待多個字元串,并對每一個字元串執行不同的操作。expect還能了解一些特殊情況,如逾時和遇到檔案結尾。

timeout

       set timeout $tout指令設定後面所有的expect指令的等待響應的逾時時間為$tout(-1參數用來關閉任何逾時設定)。expect timeout被用于表示所有比對的失敗而和一段特定長度的時間相比對。預設的timeout時間值為10s。

spawn

        spawn用來啟動一個新的程序,比如spawn ssh -D $port [email protected]$host,Expect會執行指令"ssh -D $port [email protected]$host"。從使用者的角度來看是像這樣的:當一個程序通過spawn指令啟動時,變量spawn_id就被設定成某程序的描述符。由spawn_id描述的程序就被認為是目前程序(這個描述符恰恰就是僞終端檔案的描述符,雖然使用者把它當作一個不透明的物體)。expect和send僅僅和目前程序進行互動。是以,切換一個作業所需要做的僅僅是把該程序的描述符賦給spawn_id。

send/send_user

        用來發送一個字元串,比如 send "hello world"。初始情況下,這個字元串會發送到标準輸出。一旦你的程式已經與其他程式進行互動,字元串就會被發送到其他程式那裡。send發送的指令一般需要帶換行符,在UNIX下可以是"\r"或"\n"。我們使用expect_user和send_user來進行标準I/O,同時不改變spawn_id。

expect/expect_user

       與send相反,expect用來等待你所期望的字元串。expect會一直等待下去,除非收到的字元串與預期的格式比對,或者到了逾時時間。

       expect patlist1 action1 patlist2 action2.....       該指令一直等到目前程序的輸出和以上的某一個模式相比對,或者等到時間超過一個特定的時間長度,或者等到遇到了檔案的結束為止。如果最後一個action是空的,就可以省略它。每一個patlist都由一個模式或者模式的表(lists)組成。如果有一個模式比對成功,相應的action就被執行,執行的結果從expect傳回。  

       預設情況下,expect "偵聽" STDOUT和STDERR,直到找到比對或timeout期滿為止。預設情況下,調用-glob比對方式。-glob使用Tcl的字元串比對方式來比對模式,它實作檔案名替換,支援 "[] [!] ? *" 等通配符,類似于shell模式比對。-re标志調用regexp比對,-ex表明必須是精确比對,不帶通配符或變量擴充。expect的其他選項包括-i和-nocase,前者表示要監控産生的程序,後者強迫在比對之前将程序輸出轉換為小寫。被精确比對的字元串(或者當逾時發生時,已經讀取但未進行比對的字元串)被存貯在變量expect_match裡面。如果patlist是eof或者timeout,則發生檔案結束或者逾時時才執行相應的action。預設情況下逾時的時值是10秒,但可以用類似"set timeout 30"之類的指令把逾時時值設定為30秒。

        下面的一個程式段是從一個有關登入的腳本裡面摘取的。abort是在腳本的别處定義的過程,而其他的action使用類似與C語言的Tcl原語。

expect "*welcome*" break
       "*busy*"    {print busy;continue}
       "*failed*"  abort
       timeout     abort
           

      模式是通常的C Shell風格的正規表達式。模式必須比對目前程序的從上一個expect或者interact開始的所有輸出(是以統配符*使用的非常)的普遍。但是,一旦輸出超過2000個位元組,前面的字元就會被忘記,這可以通過設定match_max的值來改變。

      expect指令确實展現了expect語言的最好和最壞的性質。特别是,expect指令的靈活性是以經常出現令人迷惑的文法做代價。除了關鍵字模式(比如說eof,timeout)那些模式表可以包括多個模式。這保證提供了一種方法來區分他們。(在Tcl裡面,如果不會出現二意性,沒有必要使用引号)。字元可以使用反斜杠來單獨的引用,反斜杠也被用于對語句的延續,如果不加反斜杠的話,語句到一行的結尾處就結束了。這和Tcl也是一緻的。Tcl在發現有開的單引号或者開的雙引号時都會繼續掃描。而且,分号可以用于在一行中分割多個語句。這乍聽起來有點讓人困惑,但是,這是解釋性語言的風格,但是,這确實是Tcl的不太漂亮的部分。

      我們使用expect_user和send_user來進行标準I/O,同時不改變spawn_id。

      expect指令使用-re選項,這個選項表示指定的字元串是一個正規表達式,而不是一個普通的字元串。

expect -re "\\\[(.*)]:"
if {$expect_out(1,string)!="/bin/tcsh"} {
 send "/bin/tcsh" 
}
           

      對于上面這個例子裡是查找一個左方括号字元(其必須進行多次轉義(escape),因為它對于expect和正則表達時來說都是特殊字元)後面跟有零個或多個字元,最後是一個右方括号字元。這裡.*表示一個或多個任意字元,将其存放在()中是因為将比對結果存放在一個變量中以實作随後的對比對結果的通路。在一個正規表達式中,可在()中包含若幹個部分并通過expect_out數組通路它們。各個部分在表達式中從左到右進行編碼,從1開始(0包含有整個比對輸出)。()可能會出現嵌套情況,這這種情況下編碼從最内層到最外層來進行的。

interact

       它會把腳本的控制權交給使用者,比如你通過腳本自動連接配接到了某個ftp,并輸入了使用者名密碼,此時需要人工輸入一些指令,就可以使用interact指令。控制權交還給使用者後,除非子程式運作結束,否則expect程式無法再取得控制權,是以必須在完成所有自動處理的部分後再将控制權交還給使用者。如果隻是在程式處理過程中要求使用者輸入資訊,可以考慮使用expect_user,以下三種方式都可以擷取輸入:

expect_user "\n"
expect_user "?*"
expect_user -re ".+"
           

sleep

       sleep n會使程式阻塞n秒。

fork

       fork指令會産生出一個子程序,而且它産生傳回值,如果傳回的是0,說明這是一個子程序,如果不為0,那麼是父程序。disconnect指令将子程序放到背景執行。注意,disconnect指令隻能對子程序使用。需要注意的是if和else的寫法,if和後面的"{"之間必須要有空格且要放在同一行,else和前面的"}" 後面的"{" 之間也必須要有空格。

 set pid [fork]

 if { $pid==0 } {

  send_user "child\n"

  disconnect

 } else {

         send_user "father\n"

 }

exec

可以用exec來調用Unix程式,并通過[exec cmd]得到指令輸出,這就為expect調用shell程式提供了接口。