Expect 教程中文版

http://blog.csdn.net/chinalinuxzend/article/details/1842588


原貼:http://blog.chinaunix.net/u/13329/showart.php?id=110100

           



Expect 教程中文版


[From] http://www.linuxeden.com/edu/doctext.php?docid=799

  本教程由*葫蘆娃*翻譯,並做了適當的修改,可以自由的用於非商業目的。

[BUG]

  有不少部分,翻譯的時候不能作到“信,達”。當然了,任何時候都沒有做到“雅”,希望各位諒解。

[原著]
 
  Don Libes: National Institute of Standards and Technology
    [email protected]

[目錄]
  
  1.摘要
  2.關鍵字
  3.簡介
  4.Expect綜述
  5.callback
  6.passwd 和一致性檢查
  7.rogue 和僞終端
  8.ftp
  9.fsck
  10.多進程控制:作業控制
  11.交互式使用Expect
  12.交互式Expect編程
  13.非交互式程序的控制
  14.Expect的速度
  15.安全方面的考慮
  16.Expect資源
  17.參考書籍

1.[摘要]

  現代的Shell對程序提供了最小限度的控制(開始,停止,等等),而把交互的特性留給了用戶。 這意味着有些程序,你不能非交互的運行,比如說passwd。 有一些程序可以非交互的運行,但在很大程度上喪失了靈活性,比如說fsck。這表明Unix的工具構造邏輯開始出現問題。Expect恰恰填補了其中的一 些裂痕,解決了在Unix環境中長期存在着的一些問題。

  Expect使用Tcl作爲語言核心。不僅如此,不管程序是交互和還是非交互的,Expect都能運用。這是一個小語言和Unix的其他工具配合起來產生強大功能的經典例子。
 
  本部分教程並不是有關Expect的實現,而是關於Expect語言本身的使用,這主要也是通過不同的腳本描述例子來體現。其中的幾個例子還例證了Expect的幾個新特徵。
 
2.[關鍵字]
  
  Expect,交互,POSIX,程序化的對話,Shell,Tcl,Unix;

3.[簡介]
 
  一個叫做fsck的Unix文件系統檢查程序,可以從Shell裏面用-y或者-n選項來執行。 在手冊[1]裏面,-y選項的定義是象這樣的。

  “對於fsck的所有問題都假定一個“yes”響應;在這樣使用的時候,必須特別的小心,因爲它實際上允許程序無條件的繼續運行,即使是遇到了一些非常嚴重的錯誤”
  
  相比之下,-n選項就安全的多,但它實際上幾乎一點用都沒有。這種接口非常的糟糕,但是卻有許多的程序都是這種風格。 文件傳輸程序ftp有一個選項可以禁止交互式的提問,以便能從一個腳本里面運行。但一旦發生了錯誤,它沒有提供的處理措施。

  Expect是一個控制交互式程序的工具。他解決了fsck的問題,用非交互的方式實現了所有交互式的功能。Expect不是特別爲fsck設計的,它也能進行類似ftp的出錯處理。

  fsck和ftp的問題向我們展示了象sh,csh和別的一些shell提供的用戶接口的侷限性。 Shell沒有提供從一個程序讀和象一個程序寫的 功能。這意味着shell可以運行fsck但只能以犧牲一部分fsck的靈活性做代價。有一些程序根本就不能被執行。比如說,如果沒有一個用戶接×××互式 的提供輸入,就沒法運行下去。其他還有象Telnet,crypt,su,rlogin等程序無法在shell腳本里面自動執行。還有很多其他的應用程序 在設計是也是要求用戶輸入的。

  Expect被設計成專門針和交互式程序的交互。一個Expect程序員可以寫一個腳本來描述程序和用戶的對話。接着Expect程序可以非交互的運 行“交互式”的程序。寫交互式程序的腳本和寫非交互式程序的腳本一樣簡單。Expect還可以用於對對話的一部分進行自動化,因爲程序的控制可以在鍵盤和 腳本之間進行切換。


bes[2]裏面有詳細的描述。簡單的說,腳本是用一種解釋性語言寫的。(也有C和C++的Expect庫可供使用,但這超出了本文的範圍).Expect提供了創建交互式進程和讀寫它們的輸入和輸出的命令。 Expect是由於它的一個同名的命令而命名的。

  Expect語言是基於Tcl的。Tcl實際上是一個子程序庫,這些子程序庫可以嵌入到程序裏從而提供語言服務。 最終的語言有點象一個典型的 Shell語言。裏面有給變量賦值的set命令,控制程序執行的if,for,continue等命令,還能進行普通的數學和字符串操作。當然了,還可以 用exec來調用Unix程序。所有這些功能,Tcl都有。Tcl在參考書籍 Outerhour[3][4]裏有詳細的描述。

  Expect是在Tcl基礎上創建起來的,它還提供了一些Tcl所沒有的命令。spawn命令激活一個Unix程序來進行交互式的運行。 send命 令向進程發送字符串。expect命令等待進程的某些字符串。 expect支持正規表達式並能同時等待多個字符串,並對每一個字符串執行不同的操作。 expect還能理解一些特殊情況,如超時和遇到文件尾。

  expect命令和Tcl的case命令的風格很相似。都是用一個字符串去匹配多個字符串。(只要有可能,新的命令總是和已有的Tcl命令相似,以使得該語言保持工具族的繼承性)。下面關於expect的定義是從手冊[5]上摘錄下來的。

      expect patlist1 action1 patlist2 action2.....

    該命令一直等到當前進程的輸出和以上的某一個模式相匹配,或者等    到時間超過一個特定的時間長度,或者等到遇到了文件的結束爲止。
    
    如果最後一個action是空的,就可以省略它。

    每一個patlist都由一個模式或者模式的表(lists)組成。如果有一個模式匹配成功,相應的action就被執行。執行的結果從expect返回。
    被精確匹配的字符串(或者當超時發生時,已經讀取但未進行匹配的字符串)被存貯在變量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裏面,如果不會出現二義性話, 沒有必要使用引號)。在expect的手冊裏面,還有一個獨立的部分來解釋這種複雜性。幸運的是:有一些很好的例子似乎阻止了這種抱怨。但是,這個複雜性 很有可能在將來的版本中再度出現。爲了增強可讀性,在本文中,提供的腳本都假定雙引號是足夠的。

  字符可以使用反斜槓來單獨的引用,反斜槓也被用於對語句的延續,如果不加反斜槓的話,語句到一行的結尾處就結束了。這和Tcl也是一致的。Tcl在發 現有開的單引號或者開的雙引號時都會繼續掃描。而且,分號可以用於在一行中分割多個語句。這乍聽起來有點讓人困惑,但是,這是解釋性語言的風格,但是,這 確實是Tcl的不太漂亮的部分。

5.[callback]

  令人非常驚訝的是,一些小的腳本如何的產生一些有用的功能。下面是一個撥電話號碼的腳本。他用來把收費反向,以便使得長途電話對計算機計費。這個腳本 用類似“expect callback.exp 12016442332”來激活。其中,腳本的名字便是callback.exp,而+1(201)644-2332是要撥的電話號碼。

    #first give the user some time to logout
    exec sleep 4
    spawn tip modem
    expect "*connected*"
    send "ATD [index $argv 1] "
    #modem takes a while to connect
    set timeout 60
    expect "*CONNECT*"

  第一行是註釋,第二行展示瞭如何調用沒有交互的Unix程序。sleep 4會使程序阻塞4秒,以使得用戶有時間來退出,因爲modem總是會回叫用戶已經使用的電話號碼。

  下面一行使用spawn命令來激活tip程序,以便使得tip的輸出能夠被expect所讀取,使得tip能從send讀輸入。一旦tip說它已經連 接上,modem就會要求去撥打大哥電話號碼。(假定modem都是賀氏兼容的,但是本腳本可以很容易的修改成能適應別的類型的modem)。不論發生了 什麼,expect都會終止。如果呼叫失敗,expect腳本可以設計成進行重試,但這裏沒有。如果呼叫成功,getty會在expect退出後檢測到 DTR,並且向用戶提示loging:。(實用的腳本往往提供更多的錯誤檢測)。

  這個腳本展示了命令行參數的使用,命令行參數存貯在一個叫做argv的表裏面(這和C語言的風格很象)。在這種情況下,第一個元素就是電話號碼。方括號使得被括起來的部分當作命令來執行,結果就替換被括起來的部分。這也和C Shell的風格很象。

  這個腳本和一個大約60K的C語言程序實現的功能相似。
    

6.[passwd和一致性檢查]

  在前面,我們提到passwd程序在缺乏用戶交互的情況下,不能運行,passwd會忽略I/O重定向,也不能嵌入到管道里邊以便能從別的程序或者文 件裏讀取輸入。這個程序堅持要求真正的與用戶進行交互。因爲安全的原因,passwd被設計成這樣,但結果導致沒有非交互式的方法來檢驗passwd。這 樣一個對系統安全至關重要的程序竟然沒有辦法進行可靠的檢驗,真實具有諷刺意味。

  passwd以一個用戶名作爲參數,交互式的提示輸入密碼。下面的expect腳本以用戶名和密碼作爲參數而非交互式的運行。

    spawn oasswd [index $argv 1]
    set password [index $argv 2]
    expect "*password:"
    send "$password "
    expect "*password:"
    send "$password "
    expect eof

  第一行以用戶名做參數啓動passwd程序,爲方便起見,第二行把密碼存到一個變量裏面。和shell類似,變量的使用也不需要提前聲明。

  在第三行,expect搜索模式"*password:",其中*允許匹配任意輸入,所以對於避免指定所有細節而言是非常有效的。 上面的程序裏沒有action,所以expect檢測到該模式後就繼續運行。

  一旦接收到提示後,下一行就就把密碼送給當前進程。表明回車。(實際上,所有的C的關於字符的約定都支持)。上面的程序中有兩個expect- send序列,因爲passwd爲了對輸入進行確認,要求進行兩次輸入。在非交互式程序裏面,這是毫無必要的,但由於假定passwd是在和用戶進行交 互,所以我們的腳本還是這樣做了。

  最後,"expect eof"這一行的作用是在passwd的輸出中搜索文件結束符,這一行語句還展示了關鍵字的匹配。另外一個關鍵字匹配就是timeout了, timeout被用於表示所有匹配的失敗而和一段特定長度的時間相匹配。在這裏eof是非常有必要的,因爲passwd被設計成會檢查它的所有I/O是否 都成功了,包括第二次輸入密碼時產生的最後一個新行。

  這個腳本已經足夠展示passwd命令的基本交互性。另外一個更加完備的例子回檢查別的一些行爲。比如說,下面的這個腳本就能檢查passwd程序的 別的幾個方面。所有的提示都進行了檢查。對垃圾輸入的檢查也進行了適當的處理。進程死亡,超乎尋常的慢響應,或者別的非預期的行爲都進行了處理。

    spawn passwd [index $argv 1]
    expect     eof            {exit 1}     
        timeout            {exit 2}    
        "*No such user.*"    {exit 3}    
        "*New password:"    
    send "[index $argv 2 "
    expect     eof            {exit 4}    
        timeout            {exit 2}    
        "*Password too long*"    {exit 5}    
        "*Password too short*"    {exit 5}    
        "*Retype ew password:"
    send "[index $argv 3] "
    expect     timeout            {exit 2}    
        "*Mismatch*"        {exit 6}    
        "*Password unchanged*"    {exit 7}    
        " "        
    expect    timeout            {exit 2}    
        "*"            {exit 6}    
        eof

   
  這個腳本退出時用一個數字來表示所發生的情況。0表示passwd程序正常運行,1表示非預期的死亡,2表示鎖定,等等。使用數字是爲了簡單起見。 expect返回字符串和返回數字是一樣簡單的,即使是派生程序自身產生的消息也是一樣的。實際上,典型的做法是把整個交互的過程存到一個文件裏面,只有 當程序的運行和預期一樣的時候才把這個文件刪除。否則這個log被留待以後進一步的檢查。

  這個passwd檢查腳本被設計成由別的腳本來驅動。這第二個腳本從一個文件裏面讀取參數和預期的結果。對於每一個輸入參數集,它調用第一個腳本並且 把結果和預期的結果相比較。(因爲這個任務是非交互的,一個普通的老式shell就可以用來解釋第二個腳本)。比如說,一個passwd的數據文件很有可 能就象下面一樣。

    passwd.exp    3    bogus    -        -
    passwd.exp    0    fred    abledabl    abledabl
    passwd.exp    5    fred    abcdefghijklm    -
    passwd.exp    5    fred    abc        -
    passwd.exp    6    fred    foobar        bar    
    passwd.exp    4    fred    ^C        -

  第一個域的名字是要被運行的迴歸腳本。第二個域是需要和結果相匹配的退出值。第三個域就是用戶名。第四個域和第五個域就是提示時應該輸入的密碼。減號 僅僅表示那裏有一個域,這個域其實絕對不會用到。在第一個行中,bogus表示用戶名是非法的,因此passwd會響應說:沒有此用戶。expect在退 出時會返回3,3恰好就是第二個域。在最後一行中,^C就是被切實的送給程序來驗證程序是否恰當的退出。

  通過這種方法,expect可以用來檢驗和調試交互式軟件,這恰恰是IEEE的POSIX 1003.2(shell和工具)的一致性檢驗所要求的。進一步的說明請參考Libes[6]。

7.[rogue 和僞終端]

  Unix用戶肯定對通過管道來和其他進程相聯繫的方式非常的熟悉(比如說:一個shell管道)。expect使用僞終端來和派生的進程相聯繫。僞終端提供了終端語義以便程序認爲他們正在和真正的終端進行I/O操作。

  比如說,BSD的探險遊戲rogue在生模式下運行,並假定在連接的另一端是一個可尋址的字符終端。可以用expect編程,使得通過使用用戶界面可以玩這個遊戲。

  rogue這個探險遊戲首先提供給你一個有各種物理屬性,比如說力量值,的角色。在大部分時間裏,力量值都是16,但在幾乎每20次裏面就會有一個力 量值是18。很多的rogue玩家都知道這一點,但沒有人願意啓動程序20次以獲得一個好的配置。下面的這個腳本就能達到這個目的。

    for {} {1} {} {
        spawn rogue
        expect "*Str:18*"    break    
            "*Str:16*"    
        close
        wait
    }
    interact

  第一行是個for循環,和C語言的控制格式很象。rogue啓動後,expect就檢查看力量值是18還是16,如果是16,程序就通過執行 close和wait來退出。這兩個命令的作用分別是關閉和僞終端的連接和等待進程退出。rogue讀到一個文件結束符就推出,從而循環繼續運行,產生一 個新的rogue遊戲來檢查。

  當一個值爲18的配置找到後,控制就推出循環並跳到最後一行腳本。interact把控制轉移給用戶以便他們能夠玩這個特定的遊戲。

  想象一下這個腳本的運行。你所能真正看到的就是20或者30個初始的配置在不到一秒鐘的時間裏掠過屏幕,最後留給你的就是一個有着很好配置的遊戲。唯一比這更好的方法就是使用調試工具來玩遊戲。

  我們很有必要認識到這樣一點:rogue是一個使用光標的圖形遊戲。expect程序員必須瞭解到:光標的運動並不一定以一種直觀的方式在屏幕上體 現。幸運的是,在我們這個例子裏,這不是一個問題。將來的對expect的改進可能會包括一個內嵌的能支持字符圖形區域的終端模擬器。


8.[ftp]

  我們使用expect寫第一個腳本並沒有打印出"Hello,World"。實際上,它實現了一些更有用的功能。它能通過非交互的方式來運行ftp。ftp是用來在支持TCP/IP的網絡上進行文件傳輸的程序。除了一些簡單的功能,一般的實現都要求用戶的參與。

  下面這個腳本從一個主機上使用匿名ftp取下一個文件來。其中,主機名是第一個參數。文件名是第二個參數。

        spawn    ftp    [index $argv 1]
        expect "*Name*"
        send     "anonymous "
        expect "*Password:*"
        send [exec whoami]
        expect "*ok*ftp>*"
        send "get [index $argv 2] "
        expect "*ftp>*"

  上面這個程序被設計成在後臺進行ftp。雖然他們在底層使用和expect類似的機制,但他們的可編程能力留待改進。因爲expect提供了高級語言,你可以對它進行修改來滿足你的特定需求。比如說,你可以加上以下功能:

    :堅持--如果連接或者傳輸失敗,你就可以每分鐘或者每小時,甚
        至可以根據其他因素,比如說用戶的負載,來進行不定期的
        重試。
    :通知--傳輸時可以通過mail,write或者其他程序來通知你,甚至
        可以通知失敗。
    :初始化-每一個用戶都可以有自己的用高級語言編寫的初始化文件
        (比如說,.ftprc)。這和C shell對.cshrc的使用很類似。

  expect還可以執行其他的更復雜的任務。比如說,他可以使用McGill大學的Archie系統。Archie是一個匿名的Telnet服務,它 提供對描述Internet上可通過匿名ftp獲取的文件的數據庫的訪問。通過使用這個服務,腳本可以詢問Archie某個特定的文件的位置,並把它從 ftp服務器上取下來。這個功能的實現只要求在上面那個腳本中加上幾行就可以。

  現在還沒有什麼已知的後臺-ftp能夠實現上面的幾項功能,能不要說所有的功能了。在expect裏面,它的實現卻是非常的簡單。“堅持”的實現只要 求在expect腳本里面加上一個循環。“通知”的實現只要執行mail和write就可以了。“初始化文件”的實現可以使用一個命令,source .ftprc,就可以了,在.ftprc裏面可以有任何的expect命令。

  雖然這些特徵可以通過在已有的程序裏面加上鉤子函數就可以,但這也不能保證每一個人的要求都能得到滿足。唯一能夠提供保證的方法就是提供一種通用的語 言。一個很好的解決方法就是把Tcl自身融入到ftp和其他的程序中間去。實際上,這本來就是Tcl的初衷。在還沒有這樣做之前,expect提供了一個 能實現大部分功能但又不需要任何重寫的方案。

9.[fsck]

  fsck是另外一個缺乏足夠的用戶接口的例子。fsck幾乎沒有提供什麼方法來預先的回答一些問題。你能做的就是給所有的問題都回答"yes"或者都回答"no"。

  下面的程序段展示了一個腳本如何的使的自動的對某些問題回答"yes",而對某些問題回答"no"。下面的這個腳本一開始先派生fsck進程,然後對其中兩種類型的問題回答"yes",而對其他的問題回答"no"。

    for {} {1} {} {
        expect
            eof        break        
            "*UNREF FILE*CLEAR?"    {send "r "}    
            "*BAD INODE*FIX?"    {send "y "}    
            "*?"            {send "n "}    
    }

  在下面這個版本里面,兩個問題的回答是不同的。而且,如果腳本遇到了什麼它不能理解的東西,就會執行interact命令把控制交給用戶。用戶的擊鍵 直接交給fsck處理。當執行完後,用戶可以通過按"+"鍵來退出或者把控制交還給expect。如果控制是交還給腳本了,腳本就會自動的控制進程的剩餘 部分的運行。

    for {} {1} {}{
        expect             
            eof        break        
            "*UNREF FILE*CLEAR?"    {send "y "}    
            "*BAD INODE*FIX?"    {send "y "}    
            "*?"            {interact +}    
    }

  如果沒有expect,fsck只有在犧牲一定功能的情況下纔可以非交互式的運行。fsck幾乎是不可編程的,但它卻是系統管理的最重要的工具。許多別的工具的用戶接口也一樣的不足。實際上,正是其中的一些程序的不足導致了expect的誕生。

10.[控制多個進程:作業控制]


  expect的作業控制概念精巧的避免了通常的實現困難。其中包括了兩個問題:一個是expect如何處理經典的作業控制,即當你在終端上按下^Z鍵時expect如何處理;另外一個就是expect是如何處理多進程的。

  對第一個問題的處理是:忽略它。expect對經典的作業控制一無所知。比如說,你派生了一個程序並且發送一個^Z給它,它就會停下來(這是僞終端的完美之處)而expect就會永遠的等下去。

  但是,實際上,這根本就不成一個問題。對於一個expect腳本,沒有必要向進程發送^Z。也就是說,沒有必要停下一個進程來。expect僅僅是忽略了一個進程,而把自己的注意力轉移到其他的地方。這就是expect的作業控制思想,這個思想也一直工作的很好。

  從用戶的角度來看是象這樣的:當一個進程通過spawn命令啓動時,變量spawn_id就被設置成某進程的描述符。由spawn_id描述的進程就 被認爲是當前進程。(這個描述符恰恰就是僞終端文件的描述符,雖然用戶把它當作一個不透明的物體)。expect和send命令僅僅和當前進程進行交互。 所以,切換一個作業所需要做的僅僅是把該進程的描述符賦給spawn_id。

  這兒有一個例子向我們展示瞭如何通過作業控制來使兩個chess進程進行交互。在派生完兩個進程之後,一個進程被通知先動一步。在下面的循環裏面,每 一步動作都送給另外一個進程。其中,read_move和write_move兩個過程留給讀者來實現。(實際上,它們的實現非常的容易,但是,由於太長 了所以沒有包含在這裏)。

    spawn chess            ;# start player one
    set id1    $spawn_id
    expect "Chess "
    send "first "            ;# force it to go first
    read_move

    spawn chess            ;# start player two
    set id2    $spawn_id
    expect "Chess "
    
    for {} {1} {}{
        send_move
        read_move
        set spawn_id    $id1
        
        send_move
        read_move
        set spawn_id    $id2
    }

   有一些應用程序和chess程序不太一樣,在chess程序裏,的兩個玩家輪流動。下面這個腳本實現了一個冒充程序。它能夠控制一個終端以便用戶能夠登錄 和正常的工作。但是,一旦系統提示輸入密碼或者輸入用戶名的時候,expect就開始把擊鍵記下來,一直到用戶按下回車鍵。這有效的收集了用戶的密碼和用 戶名,還避免了普通的冒充程序的"Incorrect password-tryagain"。而且,如果用戶連接到另外一個主機上,那些額外的登錄也會被記錄下來。

    spawn tip /dev/tty17        ;# open connection to
    set tty $spawn_id        ;# tty to be spoofed

    spawn login
    set login $spawn_id

    log_user 0
    
    for {} {1} {} {
        set ready [select $tty $login]
        
        case $login in $ready {
            set spawn_id $login
            expect         
              {"*password*" "*login*"}{
                  send_user $expect_match
                  set log 1
                 }    
              "*"        ;# ignore everything else
            set spawn_id    $tty;
            send $expect_match
        }
        case $tty in $ready {
            set spawn_id    $tty
            expect "* *"{
                    if $log {
                      send_user $expect_match
                      set log 0
                    }
                   }    
                "*" {
                    send_user $expect_match
                   }
            set spawn_id     $login;
            send $expect_match
        }
    }
        

   這個腳本是這樣工作的。首先連接到一個login進程和終端。缺省的,所有的對話都記錄到標準輸出上(通過send_user)。因爲我們對此並不感興 趣,所以,我們通過命令"log_user 0"來禁止這個功能。(有很多的命令來控制可以看見或者可以記錄的東西)。

   在循環裏面,select等待終端或者login進程上的動作,並且返回一個等待輸入的spawn_id表。如果在表裏面找到了一個值的話,case就執 行一個action。比如說,如果字符串"login"出現在login進程的輸出中,提示就會被記錄到標準輸出上,並且有一個標誌被設置以便通知腳本開 始記錄用戶的擊鍵,直至用戶按下了回車鍵。無論收到什麼,都會回顯到終端上,一個相應的action會在腳本的終端那一部分執行。

   這些例子顯示了expect的作業控制方式。通過把自己插入到對話裏面,expect可以在進程之間創建複雜的I/O流。可以創建多扇出,複用扇入的,動態的數據相關的進程圖。

   相比之下,shell使得它自己一次一行的讀取一個文件顯的很困難。shell強迫用戶按下控制鍵(比如,^C,^Z)和關鍵字(比如fg和bg)來實現 作業的切換。這些都無法從腳本里面利用。相似的是:以非交互方式運行的shell並不處理“歷史記錄”和其他一些僅僅爲交互式使用設計的特徵。這也出現了 和前面哪個passwd程序的相似問題。相似的,也無法編寫能夠迴歸的測試shell的某些動作的shell腳本。結果導致shell的這些方面無法進行 徹底的測試。

   如果使用expect的話,可以使用它的交互式的作業控制來驅動shell。一個派生的shell認爲它是在交互的運行着,所以會正常的處理作業控制。它 不僅能夠解決檢驗處理作業控制的shell和其他一些程序的問題。還能夠在必要的時候,讓shell代替expect來處理作業。可以支持使用shell 風格的作業控制來支持進程的運行。這意味着:首先派生一個shell,然後把命令送給shell來啓動進程。如果進程被掛起,比如說,發送了一個^Z,進 程就會停下來,並把控制返回給shell。對於expect而言,它還在處理同一個進程(原來那個shell)。

  expect的解決方法不僅具有很大的靈活性,它還避免了重複已經存在於shell中的作業控制軟件。通過使用shell,由於你可以選擇你想派生的 shell,所以你可以根據需要獲得作業控制權。而且,一旦你需要(比如說檢驗的時候),你就可以驅動一個shell來讓這個shell以爲它正在交互式 的運行。這一點對於在檢測到它們是否在交互式的運行之後會改變輸出的緩衝的程序來說也是很重要的。

  爲了進一步的控制,在interact執行期間,expect把控制終端(是啓動expect的那個終端,而不是僞終端)設置成生模式以便字符能夠正 確的傳送給派生的進程。當expect在沒有執行interact的時候,終端處於熟模式下,這時候作業控制就可以作用於expect本身。

11.[交互式的使用expect]

  在前面,我們提到可以通過interact命令來交互式的使用腳本。基本上來說,interact命令提供了對對話的自由訪問,但我們需要一些更精細 的控制。這一點,我們也可以使用expect來達到,因爲expect從標準輸入中讀取輸入和從進程中讀取輸入一樣的簡單。 但是,我們要使用expect_user和send_user來進行標準I/O,同時不改變spawn_id。

  下面的這個腳本在一定的時間內從標準輸入裏面讀取一行。這個腳本叫做timed_read,可以從csh裏面調用,比如說,set answer="timed_read 30"就能調用它。

    #!/usr/local/bin/expect -f
    set timeout [index $argv 1]
    expect_user "* "
    send_user $expect_match

   第三行從用戶那裏接收任何以新行符結束的任何一行。最後一行把它返回給標準輸出。如果在特定的時間內沒有得到任何鍵入,則返回也爲空。

   第一行支持"#!"的系統直接的啓動腳本。(如果把腳本的屬性加上可執行屬性則不要在腳本前面加上expect)。當然了腳本總是可以顯式的用 "expect scripot"來啓動。在-c後面的選項在任何腳本語句執行前就被執行。比如說,不要修改腳本本身,僅僅在命令行上加上-c "trace...",該腳本可以加上trace功能了(省略號表示trace的選項)。

   在命令行裏實際上可以加上多個命令,只要中間以";"分開就可以了。比如說,下面這個命令行:

    expect -c "set timeout 20;spawn foo;expect"

   一旦你把超時時限設置好而且程序啓動之後,expect就開始等待文件結束符或者20秒的超時時限。 如果遇到了文件結束符(EOF),該程序就會停下來,然後expect返回。如果是遇到了超時的情況,expect就返回。在這兩中情況裏面,都隱式的殺 死了當前進程。

   如果我們不使用expect而來實現以上兩個例子的功能的話,我們還是可以學習到很多的東西的。在這兩中情況裏面,通常的解決方案都是fork另一個睡眠 的子進程並且用signal通知原來的shell。如果這個過程或者讀先發生的話,shell就會殺司那個睡眠的進程。 傳遞pid和防止後臺進程產生啓動信息是一個讓除了高手級shell程序員之外的人頭痛的事情。提供一個通用的方法來象這樣啓動多個進程會使shell腳 本非常的複雜。 所以幾乎可以肯定的是,程序員一般都用一個專門C程序來解決這樣一個問題。

   expect_user,send_user,send_error(向標準錯誤終端輸出)在比較長的,用來把從進程來的複雜交互翻譯成簡單交互的 expect腳本里面使用的比較頻繁。在參考[7]裏面,Libs描述怎樣用腳本來安全的包裹(wrap)adb,怎樣把系統管理員從需要掌握adb的細 節裏面解脫出來,同時大大的降低了由於錯誤的擊鍵而導致的系統崩潰。

   一個簡單的例子能夠讓ftp自動的從一個私人的帳號裏面取文件。在這種情況裏,要求提供密碼。 即使文件的訪問是受限的,你也應該避免把密碼以明文的方式存儲在文件裏面。把密碼作爲腳本運行時的參數也是不合適的,因爲用ps命令能看到它們。有一個解 決的方法就是在腳本運行的開始調用expect_user來讓用戶輸入以後可能使用的密碼。這個密碼必須只能讓這個腳本知道,即使你是每個小時都要重試 ftp。

   即使信息是立即輸入進去的,這個技巧也是非常有用。比如說,你可以寫一個腳本,把你每一個主機上不同的帳號上的密碼都改掉,不管他們使用的是不是同 一個密碼數據庫。如果你要手工達到這樣一個功能的話,你必須Telnet到每一個主機上,並且手工輸入新的密碼。而使用expect,你可以只輸入密碼一 次而讓腳本來做其它的事情。

   expect_user和interact也可以在一個腳本里面混合的使用。考慮一下在調試一個程序的循環時,經過好多步之後才失敗的情況。一個 expect腳本可以驅動哪個調試器,設置好斷點,執行該程序循環的若干步,然後將控制返回給鍵盤。它也可以在返回控制之前,在循環體和條件測試之間來回 的切換.

=====================================================

[From] http://www.linuxeden.com/edu/doctext.php?docid=2288

我們通過Shell可以實現簡單的控制流功能,如:循環、判斷等。但是對於需要交互的場合則必須通過人工來干預,有時候我們可能會需要實現和交互程序如telnet服務器等進行交互的功能。而Expect就使用來實現這種功能的工具。

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

Expect是不斷髮展的,隨着時間的流逝,其功能越來越強大,已經成爲系統管理員的的一個強大助手。Expect需要Tcl編程語言的支持,要在系統上運行Expect必須首先安裝Tcl。

Expect工作原理

從最簡單的層次來說,Expect的工作方式象一個通用化的Chat腳本工具。Chat腳本最早用於UUCP網絡內,以用來實現計算機之間需要建立連接時進行特定的登錄會話的自動化。

Chat腳本由一系列expect-send對組成:expect等待輸出中輸出特定的字符,通常是一個提示符,然後發送特定的響應。例如下面的Chat 腳本實現等待標準輸出出現Login:字符串,然後發送somebody作爲用戶名;然後等待Password:提示符,併發出響應sillyme。


Login: somebody Password: sillyme


這個腳本用來實現一個登錄過程,並用特定的用戶名和密碼實現登錄。

Expect最簡單的腳本操作模式本質上和Chat腳本工作模式是一樣的。下面我們分析一個響應chsh命令的腳本。我們首先回顧一下這個交互命令的格式。假設我們爲用戶chavez改變登錄腳本,命令交互過程如下:

# chsh chavez
Changing the login shell for chavez
Enter the new value, or press return for the default
        Login Shell [/bin/bash]: /bin/tcsh
#
可以看到該命令首先輸出若干行提示信息並且提示輸入用戶新的登錄shell。我們必須在提示信息後面輸入用戶的登錄shell或者直接回車不修改登錄shell。

下面是一個能用來實現自動執行該命令的Expect腳本:

#!/usr/bin/expect
# Change a login shell to tcsh

set user [lindex $argv 0]
spawn chsh $user
   expect "]:"
   send "/bin/tcsh "
   expect eof
exit
這個簡單的腳本可以解釋很多Expect程序的特性。和其他腳本一樣首行指定用來執行該腳本的命令程序,這裏是/usr/bin/expect。程序第一行用來獲得腳本的執行參數(其保存在數組$argv中,從0號開始是參數),並將其保存到變量user中。

第二個參數使用Expect的spawn命令來啓動腳本和命令的會話,這裏啓動的是chsh命令,實際上命令是以衍生子進程的方式來運行的。

隨後的expect和send命令用來實現交互過程。腳本首先等待輸出中出現]:字符串,一旦在輸出中出現chsh輸出到的特徵字符串(一般特徵字符串往 往是等待輸入的最後的提示符的特徵信息)。對於其他不匹配的信息則會完全忽略。當腳本得到特徵字符串時,expect將發送/bin/tcsh和一個回車 符給chsh命令。最後腳本等待命令退出(chsh結束),一旦接收到標識子進程已經結束的eof字符,expect腳本也就退出結束。

決定如何響應

管理員往往有這樣的需求,希望根據當前的具體情況來以不同的方式對一個命令進行響應。我們可以通過後面的例子看到expect可以實現非常複雜的條件響應,而僅僅通過簡單的修改預處理腳本就可以實現。下面的例子是一個更復雜的expect-send例子:

expect -re "/[(.*)]:"
if {$expect_out(1,string)!="/bin/tcsh"} {
   send "/bin/tcsh" }
send " "
expect eof
在這個例子中,第一個expect命令現在使用了-re參數,這個參數表示指定的的字符串是一個正則表達式,而不是一個普通的字符串。對於上面這個例子裏 是查找一個左方括號字符(其必須進行三次逃逸(escape),因此有三個符號,因爲它對於expect和正則表達時來說都是特殊字符)後面跟有零個或多 個字符,最後是一個右方括號字符。這裏.*表示表示一個或多個任意字符,將其存放在()中是因爲將匹配結果存放在一個變量中以實現隨後的對匹配結果的訪 問。

當發現一個匹配則檢查包含在[]中的字符串,查看是否爲/bin/tcsh。如果不是則發送/bin/tcsh給chsh命令作爲輸入,如果是則僅僅發送一個回車符。這個簡單的針對具體情況發出不同相響應的小例子說明了expect的強大功能。

在一個正則表達時中,可以在()中包含若干個部分並通過expect_out數組訪問它們。各個部分在表達式中從左到右進行編碼,從1開始(0包含有整個匹配輸出)。()可能會出現嵌套情況,這這種情況下編碼從最內層到最外層來進行的。

使用超時

下一個expect例子中將闡述具有超時功能的提示符函數。這個腳本提示用戶輸入,如果在給定的時間內沒有輸入,則會超時並返回一個默認的響應。這個腳本接收三個參數:提示符字串,默認響應和超時時間(秒)。

#!/usr/bin/expect
# Prompt function with timeout and default.
set prompt [lindex $argv 0]
set def [lindex $argv 1]
set response $def
set tout [lindex $argv 2]
腳本的第一部分首先是得到運行參數並將其保存到內部變量中。

send_tty "$prompt: "
set timeout $tout
expect " " {
    set raw $expect_out(buffer)
# remove final carriage return
   set response [string trimright "$raw" " "]
   }
if {"$response" == "} {set response $def}
send "$response "
# Prompt function with timeout and default.
set prompt [lindex $argv 0]
set def [lindex $argv 1]
set response $def
set tout [lindex $argv 2]

這是腳本其餘的內容。可以看到send_tty命令用來實現在終端上顯示提示符字串和一個冒號及空格。set timeout命令設置後面所有的expect命令的等待響應的超時時間爲$tout(-l參數用來關閉任何超時設置)。

然後expect命令就等待輸出中出現回車字符。如果在超時之前得到回車符,那麼set命令就會將用戶輸入的內容賦值給變臉raw。隨後的命令將用戶輸入內容最後的回車符號去除以後賦值給變量response。

然後,如果response中內容爲空則將response值置爲默認值(如果用戶在超時以後沒有輸入或者用戶僅僅輸入了回車符)。最後send命令將response變量的值加上回車符發送給標準輸出。

一個有趣的事情是該腳本沒有使用spawn命令。 該expect腳本會與任何調用該腳本的進程交互。

如果該腳本名爲prompt,那麼它可以用在任何C風格的shell中。


% set a='prompt "Enter an answer" silence 10'
Enter an answer: test

% echo Answer was "$a"
Answer was test
prompt設定的超時爲10秒。如果超時或者用戶僅僅輸入了回車符號,echo命令將輸出

Answer was "silence"

一個更復雜的例子

下面我們將討論一個更加複雜的expect腳本例子,這個腳本使用了一些更復雜的控制結構和很多複雜的交互過程。這個例子用來實現發送write命令給任意的用戶,發送的消息來自於一個文件或者來自於鍵盤輸入。

#!/usr/bin/expect
# Write to multiple users from a prepared file
# or a message input interactively

if {$argc<2} {
   send_user "usage: $argv0 file user1 user2 ... "
   exit
}
send_user命令用來顯示使用幫助信息到父進程(一般爲用戶的shell)的標準輸出。

set nofile 0
# get filename via the Tcl lindex function
set file [lindex $argv 0]
if {$file=="i"} {
   set nofile 1
} else {
# make sure message file exists
   if {[file isfile $file]!=1} {
       send_user "$argv0: file $file not found. "
       exit }}

這部分實現處理腳本啓動參數,其必須是一個儲存要發送的消息的文件名或表示使用交互輸入得到發送消的內容的"i"命令。

變量file被設置爲腳本的第一個參數的值,是通過一個Tcl函數lindex來實現的,該函數從列表/數組得到一個特定的元素。[]用來實現將函數lindex的返回值作爲set命令的參數。

如果腳本的第一個參數是小寫的"i",那麼變量nofile被設置爲1,否則通過調用Tcl的函數isfile來驗證參數指定的文件存在,如果不存在就報錯退出。

可以看到這裏使用了if命令來實現邏輯判斷功能。該命令後面直接跟判斷條件,並且執行在判斷條件後的{}內的命令。if條件爲false時則運行else後的程序塊。

set procs {}
# start write processes
for {set i 1} {$i<$argc}
   {incr i} {
   spawn -noecho write
      [lindex $argv $i]
   lappend procs $spawn_id
   }
最後一部分使用spawn命令來啓動write進程實現向用戶發送消息。這裏使用了for命令來實現循環控制功能,循環變量首先設置爲1,然後因此遞增。 循環體是最後的{}的內容。這裏我們是用腳本的第二個和隨後的參數來spawn一個write命令,並將每個參數作爲發送消息的用戶名。lappend命 令使用保存每個spawn的進程的進程ID號的內部變量$spawn_id在變量procs中構造了一個進程ID號列表。

if {$nofile==0} {
   setmesg [open "$file" "r"]
} else {
   send_user "enter message,
   ending with ^D: " }

最後腳本根據變量nofile的值實現打開消息文件或者提示用戶輸入要發送的消息。

set timeout -1
while 1 {
   if {$nofile==0} {
      if {[gets $mesg chars] == -1} break
      set line "$chars "
} else {
      expect_user {
         -re " " {}
         eof break }
      set line $expect_out(buffer) }

   foreach spawn_id $procs {
       send $line }
   sleep 1}
exit
上面這段代碼說明了實際的消息文本是如何通過無限循環while被髮送的。while循環中的 if判斷消息是如何得到的。在非交互模式下,下一行內容從消息文件中讀出,當文件內容結束時while循環也就結束了。(break命令實現終止循環) 。

在交互模式下,expect_user命令從用戶接收消息,當用戶輸入ctrl+D時結束輸入,循環同時結束。 兩種情況下變量$line都被用來保存下一行消息內容。當是消息文件時,回車會被附加到消息的尾部。

foreach循環遍歷spawn的所有進程,這些進程的ID號都保存在列表變量$procs中,實現分別和各個進程通信。send命令組成了 foreach的循環體,發送一行消息到當前的write進程。while循環的最後是一個sleep命令,主要是用於處理非交互模式情況下,以確保消息 不會太快的發送給各個write進程。當while循環退出時,expect腳本結束。

參考資源

Expect軟件版本深帶有很多例子腳本,不但可以用於學習和理解expect腳本,而且是非常使用的工具。一般可以在 /usr/doc/packages/expect/example看到它們,在某些linux發佈中有些expect腳本保存在/usr/bin目錄 下。

Don Libes, Exploring Expect, O'Reilly & Associates, 1995.

John Ousterhout, Tcl and the Tk Toolkit, Addison-Wesley, 1994.

一些有用的expect腳本

autoexpect:這個腳本將根據自身在運行時用戶的操作而生成一個expect腳本。它的功能某種程度上類似於在Emacs編輯器的鍵盤宏工具。一個自動創建的腳本可能是創建自己定製腳本的好的開始。

kibitz:這是一個非常有用的工具。通過它兩個或更多的用戶可以連接到同一個shell進程。可以用於技術支持或者培訓(參見下圖)。

 

同樣可以用於其他一些要求同步的協同任務。例如我希望和另外一個同事一起編輯一封信件,這樣通過kibitz我們可以共享同一個運行編輯器的腳本,同時進行編輯和查看信件內容。

tkpasswd: 這個腳本提供了修改用戶密碼的GUI工具,包括可以檢查密碼是否是基於字典模式。這個工具同時是一個學習expect和tk的好實例。

==================================================
                                   

Expect 超出預期

[From] http://www-128.ibm.com/developerworks/cn/linux/server/clinic/part1/index.html
Cameron Laird 用一篇對受歡迎的 Expect 工具的概述開啓了他新的月度專欄,Expect 是一種功能大大超出大多數程序員和管理員認識的語言。Expect 非常適合保持服務器正常運轉所需的通用工作,實際上,它可以作爲一種(幾乎)通用的編程語言。通過單擊本文頂部或底部的 討論在         論壇中將您對本文的想法與作者和其他讀者一起分享。                                          

您是一名“系統程序員”― 您編寫代碼以保持服務器正常運轉,並且爲您的應用程序開發人員同事提供所需的底層功能。您從哪裏獲取所需的信息呢?大多數編程參考大全關心客戶機或者“應用程序”問題,而管理書籍通常回避編程而致力於“配置”。

我希望您會發現這一新的“服務器診所”專欄是有用的來源之一。每個月,我都將解決在服務器的“維護與支持”中遇到的一個編程問題或一類共同問題。

專 欄第一部分將 Expect 作爲您最應該瞭解的一種語言進行介紹。您可能已經熟悉 Expect 了。不過,您也可能從未見過 Expect 所管理任務的完整範圍。Expect 實現了一種 Linux 系統編程的通用性,其它語言 ― 即使是 C、Java 或 bash ― 都無法與之相比。雖然未來的專欄文章將展示使用各種語言的解決方案,但 Expect 很可能是出現頻率最高的一個。

在 Tcl 上構建

什麼使 Expect“通用”呢?首先,應瞭解 Expect 是 Tcl/Tk 編程語言的適當超集。Tcl 是在各種程序中使用的一種高級語言。 它過去通常與 Perl、Python、Ruby 和其它語言一起被歸爲“腳本編制”語言。在 2002 年,最明智的做法是拋開某些歷史事件,簡單地將所有這些語言視爲高效率的開放源碼語言。Tcl 在計算機輔助設計(CAD)領域中特別流行,象 Cisco 和 Nortel 這樣的聯網設備供應商也都使用它。與其它“腳本編制”語言一樣,Tcl 的內置功能適用於文本處理、數據庫管理、聯網和算法等領域中的最常見問題。

Tcl 是 Expect 的基礎。任何 Tcl 程序都自動是 Expect 程序。因爲有下面兩個原因,所以強調這一點很重要:

  • 許多人只知道 Expect 是一種“工具”,而從不瞭解它是一種完全成熟的編程語言。

  • 1994 年,許多真正認識到 Expect 的通用能力的程序員都被它迷住了。

Expect 的作者是(美國)國家標準與技術協會(National Institute of Standards and Technology)的 Don Libes。他在 1994 年出版了一本關於 Expect 的出色書籍。該書現在仍只有第一版,它沒有競爭者;這本書寫得太好了,以至於沒有出版商出版另一本書。最引人注目的是, Exploring Expect(請參閱本文後面的         參考資料)一直不需要更新。它的清晰和精確很好地經受了時間的考驗。

這裏的問題是,過去八年以來,Expect 的底層 Tcl 基礎已經有了極大發展。最初編寫 Expect 時,Tcl 並不追求成爲通用的編程語言。從那時起,Tcl 已經:

  • 知道如何處理完整的八位數據,甚至能方便地處理 Unicode;

  • 添加了方便的 TCP/IP 抽象;

  • 獲取了數據和時間計算以及格式化方面的能力;

  • 改進並合理化了其字符串處理;

因此,請記住:如果 Perl、Java 或 C 可以解決一個問題,那麼 Tcl 以及 Expect 很可能也可以解決。

Tcl 有一項任何其它編程語言都“無與倫比(out of the box)”的工作,這就是圖形用戶界面(GUI)的構建。雖然從 ActiveState Tools Corporation 下載的 Linux 版標準 ActiveTcl 二進制分發版只有大約 10 兆字節,但它不僅包含 Expect,而且還包含功能齊全的集成 GUI 工具箱。下面的示例將說明這個名爲“Tk”的工具箱如何簡潔地表達 GUI 解決方案。


                                   



                                               

回頁首



                                   

難題的獨特解決方案

Expect 的 Tcl/Tk 基礎適用於範圍非常廣的編程。請記住,Expect 可以完成 Tcl/Tk 所能做的一切。除此之外,Expect 添加了三大類別的附加功能:

  • 擴展的調試選項

  • 描述面向字符對話框的便利命令

  • 棘手的面向字符終端的獨一無二的管理

這些功能中第一個是常規的。Expect 有各種“開關”來記錄或報告其操作的各個方面。

Expect 的用途是使面向字符的交互自動化。您可能已經自己完成了許多這種工作。每次編寫命令行管道或重定向輸入/輸出(I/O)流時,您都在讓計算機管理這些工作,否則您必須自己輸入。

Expect 以兩種方式深化了這一控制:首先,它提供了表達對話框複雜程度的語言。Expect 不只使用固定“腳本”作爲應用程序的輸入,而是使交互的每個擊鍵都可編程。

如 Libes 所說,更關鍵的是:“最終,Expect 是爲處理蹩腳的界面而設計的工具。”特別是 Expect 具有管理抵制 I/O 重定向的應用程序的能力。典型示例是命令行          passwd 程序。每個負責管理服務器的人員遲早都需要使密碼更新自動化。第一次嘗試可能是作爲 root 用戶運行類似下面的代碼:


失敗的 passwd 自動化
                                   

 
passwd $user << HERE
$newpassword
$newpassword
HERE
                                               


                                   

正如每個嘗試它的人很快會發現,這根本不起作用。shell 的 < 和 << 重定向對於象          passwd 這樣的程序不起作用。

但是,Expect 可以使重定向起作用。Expect 知道如何與所有面向字符的應用程序對話,即使是象          passwd 那樣操縱終端設置的應用程序。

正是這一點完善了 Expect 的通用性。原則上,其它語言或庫可以提供終端特徵的信息。例如,Perl 的          Expect.pm 模塊在這方面已經做了很多。雖然經過十多年生產使用,但卻沒出現其它有力的競爭對手。

這 就是您應該學習 Expect 的原因。您將處理帶有“蹩腳界面”的程序 ― 您周圍有很多這樣的程序 ― 而 Expect 通過讓它們完成您所需的工作,可以減少幾小時甚至幾天的開發時間。同時,還可以將 Expect 用於通常由 bash 或 Perl 完成的所有作業。


                                   



                                               

回頁首



                                   

有關 Expect 的所有其它須知信息

您還應該瞭解有關 Expect 的其它信息。本專欄的最後部分包括對 Expect 侷限的說明、對解決常見問題的 Expect 工作代碼的概述以及可以引導您更深入瞭解 Expect 編程的參考。

Expect 所做的比大多數人所認識到的要多;這就是本專欄的主題。Expect 也有不足之處。系統程序員通常需要使象 FTP 操作、電子郵件發送或處理以及 GUI 測試這樣的任務自動化。對於其中的前兩項,Expect 無法提供幫助。更準確地說,雖然可以使用 Expect 來使 FTP 和電子郵件自動化(這樣做在前幾年也很常見),但是現在 Expect 在這些領域方面沒有特別優勢。其它語言和方法與面向 Expect 的編碼功效相同,或者更勝一籌。這個“服務器診所”專欄的未來部分將說明簡便聯網自動化的示例。

Expect 的著名用法是用於測試。Expect 是用於幾個高端產品(包括 gcc)質量控制中使用的 DejaGnu 系統的基礎。然而,雖然 Expect 可用於構建 GUI,並且在幾個測試框架中也很關鍵,但是通常 Expect 在用於 GUI 系統的測試框架中 起作用。

暫時回到上面提到的          passwd 問題。Expect 對它的展望是什麼呢?

要了解 Expect 源代碼,目前更簡便的做法是忽略安全性考慮事項。下面的程序需要作爲 root 用戶運行。Expect 提供有用的功能以實現更安全的操作;不過在掌握 Expect 基礎知識後更容易理解這些。

您已經知道簡單 I/O 重定向對          passwd 不起作用。何種 Expect 程序提供了更好的結果呢?


更新密碼的簡單 Expect 程序
                                   


  # Invoke as "change_password <user> <newpassword>".
  package require expect
      # Define a [proc] that can be re-used in many
      #    applications.
  proc update_one_password {user newpassword} {
      spawn passwd $user
      expect "password: "
      exp_send $newpassword/n
          # passwd insists on verifying the change,
  #    so repeat the password.
      expect "password: "
      exp_send $newpassword/n
 }
 eval update_one_password $argv
                                               


                                   

這就是 Expect 用來使程序自動化所需的全部代碼,其它語言幾乎不可能做到。再多用幾行,您可以一次對成百上千用戶進行批處理更新。這是一種常見需求;我經常被請去恢復密碼文件被嚴重毀壞的服務器,這裏說明了一種開始的方法:


簡單迭代
                                   

  
    ...
  set default_password lizard5
  set list [exec cat list_of_accounts]
  foreach account $list {
      update_one_password $account $default_password
      puts "Password for '$account' has been reset."
  }
                                               


     
                                   



                                               

回頁首



                                   

同樣適用於 GUI

給 Expect 自動化加上 GUI 外觀也只需要多加幾行。假定您想爲一名非程序員提供方便地更新密碼的應用程序。同樣忽略安全性考慮事項,這就象完成下列代碼一樣簡單:


簡單迭代
                                   


  ...
  package require Tk
  frame .account
  frame .password
  label .account.label -text Account
  entry .account.entry -textvariable account
  label .password.label -text Password
      # Show only '*', not the real characters of
      #    the entered password.
  entry .password.entry -textvariable password -show *
  button .button -text "Update account" -command {
      update_one_password $account $password
  }
  pack .account .password .button -side top
  pack .account.label .account.entry -side left
  pack .password.label .password.entry -side left    
                                               


                                   

這個小工作應用程序具有下面的視覺外觀:


簡單 Expect 密碼管理器的抓屏
        簡單 Expect 密碼管理器的抓屏      
     
                                   



                                               

回頁首



                                   

結束語

Expect 具有系統程序員通常需要的獨特能力。同時,Expect 是出色的通用編程語言,它在聯網和 GUI 構造方面具有優勢。如果您必須只選擇一種用於日常工作的語言,Expect 近乎是理想選擇。

請告訴我您如何使用 Expect 以及想讓它爲您做什麼。在以後的幾個月,這個“服務器診所”專欄將回頭研究高端版本控制、網絡代理和更多的自動化。在那以前,祝您和您的服務器都“身體健康”。


                                   

關於作者


Cameron Laird 是 Phaseit, Inc 的一名全職顧問。他經常就開放源碼和其它技術主題撰寫文章並發表演說。Cameron 感謝 SuSE 的 Reinhard Max 在他準備這個專欄期間所提供的幫助。可以通過         [email protected]與 Cameron 聯繫。



創建於: 2006-05-09 09:13:52,修改於: 2006-05-09 09:54:03,已瀏覽315次,有評論0條


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章