<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    weidagang2046的專欄

    物格而后知致
    隨筆 - 8, 文章 - 409, 評(píng)論 - 101, 引用 - 0
    數(shù)據(jù)加載中……

    Expect 教程中文版

    [版權(quán)聲明]
      
      Copyright(c) 1999
        
      本教程由*葫蘆娃*翻譯,并做了適當(dāng)?shù)男薷模梢宰杂傻挠糜诜巧虡I(yè)目的。
      但Redistribution時(shí)必須拷貝本[版權(quán)聲明]。      

    [BUG]

      有不少部分,翻譯的時(shí)候不能作到“信,達(dá)”。當(dāng)然了,任何時(shí)候都沒(méi)有做到“雅”,希望各位諒解。

    [原著]
     
      Don Libes: National Institute of Standards and Technology
        libes@cme.nist.gov

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

    1.[摘要]

      現(xiàn)代的Shell對(duì)程序提供了最小限度的控制(開(kāi)始,停止,等等),而把交互的特性留給了用戶。 這意味著有些程序,你不能非交互的運(yùn)行,比如說(shuō)passwd。 有一些程序可以非交互的運(yùn)行,但在很大程度上喪失了靈活性,比如說(shuō)fsck。這表明Unix的工具構(gòu)造邏輯開(kāi)始出現(xiàn)問(wèn)題。Expect恰恰填補(bǔ)了其中的一些裂痕,解決了在Unix環(huán)境中長(zhǎng)期存在著的一些問(wèn)題。

      Expect使用Tcl作為語(yǔ)言核心。不僅如此,不管程序是交互和還是非交互的,Expect都能運(yùn)用。這是一個(gè)小語(yǔ)言和Unix的其他工具配合起來(lái)產(chǎn)生強(qiáng)大功能的經(jīng)典例子。
     
      本部分教程并不是有關(guān)Expect的實(shí)現(xiàn),而是關(guān)于Expect語(yǔ)言本身的使用,這主要也是通過(guò)不同的腳本描述例子來(lái)體現(xiàn)。其中的幾個(gè)例子還例證了Expect的幾個(gè)新特征。
     
    2.[關(guān)鍵字]
      
      Expect,交互,POSIX,程序化的對(duì)話,Shell,Tcl,Unix;

    3.[簡(jiǎn)介]
     
      一個(gè)叫做fsck的Unix文件系統(tǒng)檢查程序,可以從Shell里面用-y或者-n選項(xiàng)來(lái)執(zhí)行。 在手冊(cè)[1]里面,-y選項(xiàng)的定義是象這樣的。

      “對(duì)于fsck的所有問(wèn)題都假定一個(gè)“yes”響應(yīng);在這樣使用的時(shí)候,必須特別的小心,因?yàn)樗鼘?shí)際上允許程序無(wú)條件的繼續(xù)運(yùn)行,即使是遇到了一些非常嚴(yán)重的錯(cuò)誤”
      
      相比之下,-n選項(xiàng)就安全的多,但它實(shí)際上幾乎一點(diǎn)用都沒(méi)有。這種接口非常的糟糕,但是卻有許多的程序都是這種風(fēng)格。 文件傳輸程序ftp有一個(gè)選項(xiàng)可以禁止交互式的提問(wèn),以便能從一個(gè)腳本里面運(yùn)行。但一旦發(fā)生了錯(cuò)誤,它沒(méi)有提供的處理措施。

      Expect是一個(gè)控制交互式程序的工具。他解決了fsck的問(wèn)題,用非交互的方式實(shí)現(xiàn)了所有交互式的功能。Expect不是特別為fsck設(shè)計(jì)的,它也能進(jìn)行類似ftp的出錯(cuò)處理。

      fsck和ftp的問(wèn)題向我們展示了象sh,csh和別的一些shell提供的用戶接口的局限性。 Shell沒(méi)有提供從一個(gè)程序讀和象一個(gè)程序?qū)懙墓δ堋_@意味著shell可以運(yùn)行fsck但只能以犧牲一部分fsck的靈活性做代價(jià)。有一些程序根本就不能被執(zhí)行。比如說(shuō),如果沒(méi)有一個(gè)用戶接口交互式的提供輸入,就沒(méi)法運(yùn)行下去。其他還有象Telnet,crypt,su,rlogin等程序無(wú)法在shell腳本里面自動(dòng)執(zhí)行。還有很多其他的應(yīng)用程序在設(shè)計(jì)是也是要求用戶輸入的。

      Expect被設(shè)計(jì)成專門針和交互式程序的交互。一個(gè)Expect程序員可以寫一個(gè)腳本來(lái)描述程序和用戶的對(duì)話。接著Expect程序可以非交互的運(yùn)行“交互式”的程序。寫交互式程序的腳本和寫非交互式程序的腳本一樣簡(jiǎn)單。Expect還可以用于對(duì)對(duì)話的一部分進(jìn)行自動(dòng)化,因?yàn)槌绦虻目刂瓶梢栽阪I盤和腳本之間進(jìn)行切換。

    bes[2]里面有詳細(xì)的描述。簡(jiǎn)單的說(shuō),腳本是用一種解釋性語(yǔ)言寫的。(也有C和C++的Expect庫(kù)可供使用,但這超出了本文的范圍).Expect提供了創(chuàng)建交互式進(jìn)程和讀寫它們的輸入和輸出的命令。 Expect是由于它的一個(gè)同名的命令而命名的。

      Expect語(yǔ)言是基于Tcl的。Tcl實(shí)際上是一個(gè)子程序庫(kù),這些子程序庫(kù)可以嵌入到程序里從而提供語(yǔ)言服務(wù)。 最終的語(yǔ)言有點(diǎn)象一個(gè)典型的Shell語(yǔ)言。里面有給變量賦值的set命令,控制程序執(zhí)行的if,for,continue等命令,還能進(jìn)行普通的數(shù)學(xué)和字符串操作。當(dāng)然了,還可以用exec來(lái)調(diào)用Unix程序。所有這些功能,Tcl都有。Tcl在參考書(shū)籍 Outerhour[3][4]里有詳細(xì)的描述。

      Expect是在Tcl基礎(chǔ)上創(chuàng)建起來(lái)的,它還提供了一些Tcl所沒(méi)有的命令。spawn命令激活一個(gè)Unix程序來(lái)進(jìn)行交互式的運(yùn)行。 send命令向進(jìn)程發(fā)送字符串。expect命令等待進(jìn)程的某些字符串。 expect支持正規(guī)表達(dá)式并能同時(shí)等待多個(gè)字符串,并對(duì)每一個(gè)字符串執(zhí)行不同的操作。expect還能理解一些特殊情況,如超時(shí)和遇到文件尾。

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

          expect patlist1 action1 patlist2 action2.....

        該命令一直等到當(dāng)前進(jìn)程的輸出和以上的某一個(gè)模式相匹配,或者等    到時(shí)間超過(guò)一個(gè)特定的時(shí)間長(zhǎng)度,或者等到遇到了文件的結(jié)束為止。
        
        如果最后一個(gè)action是空的,就可以省略它。

        每一個(gè)patlist都由一個(gè)模式或者模式的表(lists)組成。如果有一個(gè)模式匹配成功,相應(yīng)的action就被執(zhí)行。執(zhí)行的結(jié)果從expect返回。
        被精確匹配的字符串(或者當(dāng)超時(shí)發(fā)生時(shí),已經(jīng)讀取但未進(jìn)行匹配的字符串)被存貯在變量expect_match里面。如果patlist是eof或者timeout,則發(fā)生文件結(jié)束或者超時(shí)時(shí)才執(zhí)行相應(yīng)的action.一般超時(shí)的時(shí)值是10秒,但可以用類似"set timeout 30"之類的命令把超時(shí)時(shí)值設(shè)定為30秒。
        
        下面的一個(gè)程序段是從一個(gè)有關(guān)登錄的腳本里面摘取的。abort是在腳本的別處定義的過(guò)程,而其他的action使用類似與C語(yǔ)言的Tcl原語(yǔ)。

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

        模式是通常的C Shell風(fēng)格的正規(guī)表達(dá)式。模式必須匹配當(dāng)前進(jìn)程的從上一個(gè)expect或者interact開(kāi)始的所有輸出(所以統(tǒng)配符*使用的非常)的普遍。但是,一旦輸出超過(guò)2000個(gè)字節(jié),前面的字符就會(huì)被忘記,這可以通過(guò)設(shè)定match_max的值來(lái)改變。

      expect命令確實(shí)體現(xiàn)了expect語(yǔ)言的最好和最壞的性質(zhì)。特別是,expect命令的靈活性是以經(jīng)常出現(xiàn)令人迷惑的語(yǔ)法做代價(jià)。除了關(guān)鍵字模式(比如說(shuō)eof,timeout)那些模式表可以包括多個(gè)模式。這保證提供了一種方法來(lái)區(qū)分他們。但是分開(kāi)這些表需要額外的掃描,如果沒(méi)有恰當(dāng)?shù)挠肹"]括起來(lái),這有可能會(huì)把和當(dāng)成空白字符。由于Tcl提供了兩種字符串引用的方法:?jiǎn)我碗p引,情況變的更糟。(在Tcl里面,如果不會(huì)出現(xiàn)二義性話,沒(méi)有必要使用引號(hào))。在expect的手冊(cè)里面,還有一個(gè)獨(dú)立的部分來(lái)解釋這種復(fù)雜性。幸運(yùn)的是:有一些很好的例子似乎阻止了這種抱怨。但是,這個(gè)復(fù)雜性很有可能在將來(lái)的版本中再度出現(xiàn)。為了增強(qiáng)可讀性,在本文中,提供的腳本都假定雙引號(hào)是足夠的。

      字符可以使用反斜杠來(lái)單獨(dú)的引用,反斜杠也被用于對(duì)語(yǔ)句的延續(xù),如果不加反斜杠的話,語(yǔ)句到一行的結(jié)尾處就結(jié)束了。這和Tcl也是一致的。Tcl在發(fā)現(xiàn)有開(kāi)的單引號(hào)或者開(kāi)的雙引號(hào)時(shí)都會(huì)繼續(xù)掃描。而且,分號(hào)可以用于在一行中分割多個(gè)語(yǔ)句。這乍聽(tīng)起來(lái)有點(diǎn)讓人困惑,但是,這是解釋性語(yǔ)言的風(fēng)格,但是,這確實(shí)是Tcl的不太漂亮的部分。

    5.[callback]

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

        #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*"

      第一行是注釋,第二行展示了如何調(diào)用沒(méi)有交互的Unix程序。sleep 4會(huì)使程序阻塞4秒,以使得用戶有時(shí)間來(lái)退出,因?yàn)閙odem總是會(huì)回叫用戶已經(jīng)使用的電話號(hào)碼。

      下面一行使用spawn命令來(lái)激活tip程序,以便使得tip的輸出能夠被expect所讀取,使得tip能從send讀輸入。一旦tip說(shuō)它已經(jīng)連接上,modem就會(huì)要求去撥打大哥電話號(hào)碼。(假定modem都是賀氏兼容的,但是本腳本可以很容易的修改成能適應(yīng)別的類型的modem)。不論發(fā)生了什么,expect都會(huì)終止。如果呼叫失敗,expect腳本可以設(shè)計(jì)成進(jìn)行重試,但這里沒(méi)有。如果呼叫成功,getty會(huì)在expect退出后檢測(cè)到DTR,并且向用戶提示loging:。(實(shí)用的腳本往往提供更多的錯(cuò)誤檢測(cè))。

      這個(gè)腳本展示了命令行參數(shù)的使用,命令行參數(shù)存貯在一個(gè)叫做argv的表里面(這和C語(yǔ)言的風(fēng)格很象)。在這種情況下,第一個(gè)元素就是電話號(hào)碼。方括號(hào)使得被括起來(lái)的部分當(dāng)作命令來(lái)執(zhí)行,結(jié)果就替換被括起來(lái)的部分。這也和C Shell的風(fēng)格很象。

      這個(gè)腳本和一個(gè)大約60K的C語(yǔ)言程序?qū)崿F(xiàn)的功能相似。
        

    6.[passwd和一致性檢查]

      在前面,我們提到passwd程序在缺乏用戶交互的情況下,不能運(yùn)行,passwd會(huì)忽略I/O重定向,也不能嵌入到管道里邊以便能從別的程序或者文件里讀取輸入。這個(gè)程序堅(jiān)持要求真正的與用戶進(jìn)行交互。因?yàn)榘踩脑颍琾asswd被設(shè)計(jì)成這樣,但結(jié)果導(dǎo)致沒(méi)有非交互式的方法來(lái)檢驗(yàn)passwd。這樣一個(gè)對(duì)系統(tǒng)安全至關(guān)重要的程序竟然沒(méi)有辦法進(jìn)行可靠的檢驗(yàn),真實(shí)具有諷刺意味。

      passwd以一個(gè)用戶名作為參數(shù),交互式的提示輸入密碼。下面的expect腳本以用戶名和密碼作為參數(shù)而非交互式的運(yùn)行。

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

      第一行以用戶名做參數(shù)啟動(dòng)passwd程序,為方便起見(jiàn),第二行把密碼存到一個(gè)變量里面。和shell類似,變量的使用也不需要提前聲明。

      在第三行,expect搜索模式"*password:",其中*允許匹配任意輸入,所以對(duì)于避免指定所有細(xì)節(jié)而言是非常有效的。 上面的程序里沒(méi)有action,所以expect檢測(cè)到該模式后就繼續(xù)運(yùn)行。

      一旦接收到提示后,下一行就就把密碼送給當(dāng)前進(jìn)程。表明回車。(實(shí)際上,所有的C的關(guān)于字符的約定都支持)。上面的程序中有兩個(gè)expect-send序列,因?yàn)閜asswd為了對(duì)輸入進(jìn)行確認(rèn),要求進(jìn)行兩次輸入。在非交互式程序里面,這是毫無(wú)必要的,但由于假定passwd是在和用戶進(jìn)行交互,所以我們的腳本還是這樣做了。

      最后,"expect eof"這一行的作用是在passwd的輸出中搜索文件結(jié)束符,這一行語(yǔ)句還展示了關(guān)鍵字的匹配。另外一個(gè)關(guān)鍵字匹配就是timeout了,timeout被用于表示所有匹配的失敗而和一段特定長(zhǎng)度的時(shí)間相匹配。在這里eof是非常有必要的,因?yàn)閜asswd被設(shè)計(jì)成會(huì)檢查它的所有I/O是否都成功了,包括第二次輸入密碼時(shí)產(chǎn)生的最后一個(gè)新行。

      這個(gè)腳本已經(jīng)足夠展示passwd命令的基本交互性。另外一個(gè)更加完備的例子回檢查別的一些行為。比如說(shuō),下面的這個(gè)腳本就能檢查passwd程序的別的幾個(gè)方面。所有的提示都進(jìn)行了檢查。對(duì)垃圾輸入的檢查也進(jìn)行了適當(dāng)?shù)奶幚怼_M(jìn)程死亡,超乎尋常的慢響應(yīng),或者別的非預(yù)期的行為都進(jìn)行了處理。

        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

       
      這個(gè)腳本退出時(shí)用一個(gè)數(shù)字來(lái)表示所發(fā)生的情況。0表示passwd程序正常運(yùn)行,1表示非預(yù)期的死亡,2表示鎖定,等等。使用數(shù)字是為了簡(jiǎn)單起見(jiàn)。expect返回字符串和返回?cái)?shù)字是一樣簡(jiǎn)單的,即使是派生程序自身產(chǎn)生的消息也是一樣的。實(shí)際上,典型的做法是把整個(gè)交互的過(guò)程存到一個(gè)文件里面,只有當(dāng)程序的運(yùn)行和預(yù)期一樣的時(shí)候才把這個(gè)文件刪除。否則這個(gè)log被留待以后進(jìn)一步的檢查。

      這個(gè)passwd檢查腳本被設(shè)計(jì)成由別的腳本來(lái)驅(qū)動(dòng)。這第二個(gè)腳本從一個(gè)文件里面讀取參數(shù)和預(yù)期的結(jié)果。對(duì)于每一個(gè)輸入?yún)?shù)集,它調(diào)用第一個(gè)腳本并且把結(jié)果和預(yù)期的結(jié)果相比較。(因?yàn)檫@個(gè)任務(wù)是非交互的,一個(gè)普通的老式shell就可以用來(lái)解釋第二個(gè)腳本)。比如說(shuō),一個(gè)passwd的數(shù)據(jù)文件很有可能就象下面一樣。

        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        -

      第一個(gè)域的名字是要被運(yùn)行的回歸腳本。第二個(gè)域是需要和結(jié)果相匹配的退出值。第三個(gè)域就是用戶名。第四個(gè)域和第五個(gè)域就是提示時(shí)應(yīng)該輸入的密碼。減號(hào)僅僅表示那里有一個(gè)域,這個(gè)域其實(shí)絕對(duì)不會(huì)用到。在第一個(gè)行中,bogus表示用戶名是非法的,因此passwd會(huì)響應(yīng)說(shuō):沒(méi)有此用戶。expect在退出時(shí)會(huì)返回3,3恰好就是第二個(gè)域。在最后一行中,^C就是被切實(shí)的送給程序來(lái)驗(yàn)證程序是否恰當(dāng)?shù)耐顺觥?br />
      通過(guò)這種方法,expect可以用來(lái)檢驗(yàn)和調(diào)試交互式軟件,這恰恰是IEEE的POSIX 1003.2(shell和工具)的一致性檢驗(yàn)所要求的。進(jìn)一步的說(shuō)明請(qǐng)參考Libes[6]。

    7.[rogue 和偽終端]

      Unix用戶肯定對(duì)通過(guò)管道來(lái)和其他進(jìn)程相聯(lián)系的方式非常的熟悉(比如說(shuō):一個(gè)shell管道)。expect使用偽終端來(lái)和派生的進(jìn)程相聯(lián)系。偽終端提供了終端語(yǔ)義以便程序認(rèn)為他們正在和真正的終端進(jìn)行I/O操作。

      比如說(shuō),BSD的探險(xiǎn)游戲rogue在生模式下運(yùn)行,并假定在連接的另一端是一個(gè)可尋址的字符終端。可以用expect編程,使得通過(guò)使用用戶界面可以玩這個(gè)游戲。

      rogue這個(gè)探險(xiǎn)游戲首先提供給你一個(gè)有各種物理屬性,比如說(shuō)力量值,的角色。在大部分時(shí)間里,力量值都是16,但在幾乎每20次里面就會(huì)有一個(gè)力量值是18。很多的rogue玩家都知道這一點(diǎn),但沒(méi)有人愿意啟動(dòng)程序20次以獲得一個(gè)好的配置。下面的這個(gè)腳本就能達(dá)到這個(gè)目的。

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

      第一行是個(gè)for循環(huán),和C語(yǔ)言的控制格式很象。rogue啟動(dòng)后,expect就檢查看力量值是18還是16,如果是16,程序就通過(guò)執(zhí)行close和wait來(lái)退出。這兩個(gè)命令的作用分別是關(guān)閉和偽終端的連接和等待進(jìn)程退出。rogue讀到一個(gè)文件結(jié)束符就推出,從而循環(huán)繼續(xù)運(yùn)行,產(chǎn)生一個(gè)新的rogue游戲來(lái)檢查。

      當(dāng)一個(gè)值為18的配置找到后,控制就推出循環(huán)并跳到最后一行腳本。interact把控制轉(zhuǎn)移給用戶以便他們能夠玩這個(gè)特定的游戲。

      想象一下這個(gè)腳本的運(yùn)行。你所能真正看到的就是20或者30個(gè)初始的配置在不到一秒鐘的時(shí)間里掠過(guò)屏幕,最后留給你的就是一個(gè)有著很好配置的游戲。唯一比這更好的方法就是使用調(diào)試工具來(lái)玩游戲。

      我們很有必要認(rèn)識(shí)到這樣一點(diǎn):rogue是一個(gè)使用光標(biāo)的圖形游戲。expect程序員必須了解到:光標(biāo)的運(yùn)動(dòng)并不一定以一種直觀的方式在屏幕上體現(xiàn)。幸運(yùn)的是,在我們這個(gè)例子里,這不是一個(gè)問(wèn)題。將來(lái)的對(duì)expect的改進(jìn)可能會(huì)包括一個(gè)內(nèi)嵌的能支持字符圖形區(qū)域的終端模擬器。

    8.[ftp]

      我們使用expect寫第一個(gè)腳本并沒(méi)有打印出"Hello,World"。實(shí)際上,它實(shí)現(xiàn)了一些更有用的功能。它能通過(guò)非交互的方式來(lái)運(yùn)行ftp。ftp是用來(lái)在支持TCP/IP的網(wǎng)絡(luò)上進(jìn)行文件傳輸?shù)某绦颉3艘恍┖?jiǎn)單的功能,一般的實(shí)現(xiàn)都要求用戶的參與。

      下面這個(gè)腳本從一個(gè)主機(jī)上使用匿名ftp取下一個(gè)文件來(lái)。其中,主機(jī)名是第一個(gè)參數(shù)。文件名是第二個(gè)參數(shù)。

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

      上面這個(gè)程序被設(shè)計(jì)成在后臺(tái)進(jìn)行ftp。雖然他們?cè)诘讓邮褂煤蚭xpect類似的機(jī)制,但他們的可編程能力留待改進(jìn)。因?yàn)閑xpect提供了高級(jí)語(yǔ)言,你可以對(duì)它進(jìn)行修改來(lái)滿足你的特定需求。比如說(shuō),你可以加上以下功能:

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

      expect還可以執(zhí)行其他的更復(fù)雜的任務(wù)。比如說(shuō),他可以使用McGill大學(xué)的Archie系統(tǒng)。Archie是一個(gè)匿名的Telnet服務(wù),它提供對(duì)描述Internet上可通過(guò)匿名ftp獲取的文件的數(shù)據(jù)庫(kù)的訪問(wèn)。通過(guò)使用這個(gè)服務(wù),腳本可以詢問(wèn)Archie某個(gè)特定的文件的位置,并把它從ftp服務(wù)器上取下來(lái)。這個(gè)功能的實(shí)現(xiàn)只要求在上面那個(gè)腳本中加上幾行就可以。

      現(xiàn)在還沒(méi)有什么已知的后臺(tái)-ftp能夠?qū)崿F(xiàn)上面的幾項(xiàng)功能,能不要說(shuō)所有的功能了。在expect里面,它的實(shí)現(xiàn)卻是非常的簡(jiǎn)單。“堅(jiān)持”的實(shí)現(xiàn)只要求在expect腳本里面加上一個(gè)循環(huán)。“通知”的實(shí)現(xiàn)只要執(zhí)行mail和write就可以了。“初始化文件”的實(shí)現(xiàn)可以使用一個(gè)命令,source .ftprc,就可以了,在.ftprc里面可以有任何的expect命令。

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

    9.[fsck]

      fsck是另外一個(gè)缺乏足夠的用戶接口的例子。fsck幾乎沒(méi)有提供什么方法來(lái)預(yù)先的回答一些問(wèn)題。你能做的就是給所有的問(wèn)題都回答"yes"或者都回答"no"。

      下面的程序段展示了一個(gè)腳本如何的使的自動(dòng)的對(duì)某些問(wèn)題回答"yes",而對(duì)某些問(wèn)題回答"no"。下面的這個(gè)腳本一開(kāi)始先派生fsck進(jìn)程,然后對(duì)其中兩種類型的問(wèn)題回答"yes",而對(duì)其他的問(wèn)題回答"no"。

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

      在下面這個(gè)版本里面,兩個(gè)問(wèn)題的回答是不同的。而且,如果腳本遇到了什么它不能理解的東西,就會(huì)執(zhí)行interact命令把控制交給用戶。用戶的擊鍵直接交給fsck處理。當(dāng)執(zhí)行完后,用戶可以通過(guò)按"+"鍵來(lái)退出或者把控制交還給expect。如果控制是交還給腳本了,腳本就會(huì)自動(dòng)的控制進(jìn)程的剩余部分的運(yùn)行。

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

      如果沒(méi)有expect,fsck只有在犧牲一定功能的情況下才可以非交互式的運(yùn)行。fsck幾乎是不可編程的,但它卻是系統(tǒng)管理的最重要的工具。許多別的工具的用戶接口也一樣的不足。實(shí)際上,正是其中的一些程序的不足導(dǎo)致了expect的誕生。

    10.[控制多個(gè)進(jìn)程:作業(yè)控制]


      expect的作業(yè)控制概念精巧的避免了通常的實(shí)現(xiàn)困難。其中包括了兩個(gè)問(wèn)題:一個(gè)是expect如何處理經(jīng)典的作業(yè)控制,即當(dāng)你在終端上按下^Z鍵時(shí)expect如何處理;另外一個(gè)就是expect是如何處理多進(jìn)程的。

      對(duì)第一個(gè)問(wèn)題的處理是:忽略它。expect對(duì)經(jīng)典的作業(yè)控制一無(wú)所知。比如說(shuō),你派生了一個(gè)程序并且發(fā)送一個(gè)^Z給它,它就會(huì)停下來(lái)(這是偽終端的完美之處)而expect就會(huì)永遠(yuǎn)的等下去。

      但是,實(shí)際上,這根本就不成一個(gè)問(wèn)題。對(duì)于一個(gè)expect腳本,沒(méi)有必要向進(jìn)程發(fā)送^Z。也就是說(shuō),沒(méi)有必要停下一個(gè)進(jìn)程來(lái)。expect僅僅是忽略了一個(gè)進(jìn)程,而把自己的注意力轉(zhuǎn)移到其他的地方。這就是expect的作業(yè)控制思想,這個(gè)思想也一直工作的很好。

      從用戶的角度來(lái)看是象這樣的:當(dāng)一個(gè)進(jìn)程通過(guò)spawn命令啟動(dòng)時(shí),變量spawn_id就被設(shè)置成某進(jìn)程的描述符。由spawn_id描述的進(jìn)程就被認(rèn)為是當(dāng)前進(jìn)程。(這個(gè)描述符恰恰就是偽終端文件的描述符,雖然用戶把它當(dāng)作一個(gè)不透明的物體)。expect和send命令僅僅和當(dāng)前進(jìn)程進(jìn)行交互。所以,切換一個(gè)作業(yè)所需要做的僅僅是把該進(jìn)程的描述符賦給spawn_id。

      這兒有一個(gè)例子向我們展示了如何通過(guò)作業(yè)控制來(lái)使兩個(gè)chess進(jìn)程進(jìn)行交互。在派生完兩個(gè)進(jìn)程之后,一個(gè)進(jìn)程被通知先動(dòng)一步。在下面的循環(huán)里面,每一步動(dòng)作都送給另外一個(gè)進(jìn)程。其中,read_move和write_move兩個(gè)過(guò)程留給讀者來(lái)實(shí)現(xiàn)。(實(shí)際上,它們的實(shí)現(xiàn)非常的容易,但是,由于太長(zhǎng)了所以沒(méi)有包含在這里)。

        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
        }

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

        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
            }
        }
            

       這個(gè)腳本是這樣工作的。首先連接到一個(gè)login進(jìn)程和終端。缺省的,所有的對(duì)話都記錄到標(biāo)準(zhǔn)輸出上(通過(guò)send_user)。因?yàn)槲覀儗?duì)此并不感興趣,所以,我們通過(guò)命令"log_user 0"來(lái)禁止這個(gè)功能。(有很多的命令來(lái)控制可以看見(jiàn)或者可以記錄的東西)。

       在循環(huán)里面,select等待終端或者login進(jìn)程上的動(dòng)作,并且返回一個(gè)等待輸入的spawn_id表。如果在表里面找到了一個(gè)值的話,case就執(zhí)行一個(gè)action。比如說(shuō),如果字符串"login"出現(xiàn)在login進(jìn)程的輸出中,提示就會(huì)被記錄到標(biāo)準(zhǔn)輸出上,并且有一個(gè)標(biāo)志被設(shè)置以便通知腳本開(kāi)始記錄用戶的擊鍵,直至用戶按下了回車鍵。無(wú)論收到什么,都會(huì)回顯到終端上,一個(gè)相應(yīng)的action會(huì)在腳本的終端那一部分執(zhí)行。

       這些例子顯示了expect的作業(yè)控制方式。通過(guò)把自己插入到對(duì)話里面,expect可以在進(jìn)程之間創(chuàng)建復(fù)雜的I/O流。可以創(chuàng)建多扇出,復(fù)用扇入的,動(dòng)態(tài)的數(shù)據(jù)相關(guān)的進(jìn)程圖。

       相比之下,shell使得它自己一次一行的讀取一個(gè)文件顯的很困難。shell強(qiáng)迫用戶按下控制鍵(比如,^C,^Z)和關(guān)鍵字(比如fg和bg)來(lái)實(shí)現(xiàn)作業(yè)的切換。這些都無(wú)法從腳本里面利用。相似的是:以非交互方式運(yùn)行的shell并不處理“歷史記錄”和其他一些僅僅為交互式使用設(shè)計(jì)的特征。這也出現(xiàn)了和前面哪個(gè)passwd程序的相似問(wèn)題。相似的,也無(wú)法編寫能夠回歸的測(cè)試shell的某些動(dòng)作的shell腳本。結(jié)果導(dǎo)致shell的這些方面無(wú)法進(jìn)行徹底的測(cè)試。

       如果使用expect的話,可以使用它的交互式的作業(yè)控制來(lái)驅(qū)動(dòng)shell。一個(gè)派生的shell認(rèn)為它是在交互的運(yùn)行著,所以會(huì)正常的處理作業(yè)控制。它不僅能夠解決檢驗(yàn)處理作業(yè)控制的shell和其他一些程序的問(wèn)題。還能夠在必要的時(shí)候,讓shell代替expect來(lái)處理作業(yè)。可以支持使用shell風(fēng)格的作業(yè)控制來(lái)支持進(jìn)程的運(yùn)行。這意味著:首先派生一個(gè)shell,然后把命令送給shell來(lái)啟動(dòng)進(jìn)程。如果進(jìn)程被掛起,比如說(shuō),發(fā)送了一個(gè)^Z,進(jìn)程就會(huì)停下來(lái),并把控制返回給shell。對(duì)于expect而言,它還在處理同一個(gè)進(jìn)程(原來(lái)那個(gè)shell)。

      expect的解決方法不僅具有很大的靈活性,它還避免了重復(fù)已經(jīng)存在于shell中的作業(yè)控制軟件。通過(guò)使用shell,由于你可以選擇你想派生的shell,所以你可以根據(jù)需要獲得作業(yè)控制權(quán)。而且,一旦你需要(比如說(shuō)檢驗(yàn)的時(shí)候),你就可以驅(qū)動(dòng)一個(gè)shell來(lái)讓這個(gè)shell以為它正在交互式的運(yùn)行。這一點(diǎn)對(duì)于在檢測(cè)到它們是否在交互式的運(yùn)行之后會(huì)改變輸出的緩沖的程序來(lái)說(shuō)也是很重要的。

      為了進(jìn)一步的控制,在interact執(zhí)行期間,expect把控制終端(是啟動(dòng)expect的那個(gè)終端,而不是偽終端)設(shè)置成生模式以便字符能夠正確的傳送給派生的進(jìn)程。當(dāng)expect在沒(méi)有執(zhí)行interact的時(shí)候,終端處于熟模式下,這時(shí)候作業(yè)控制就可以作用于expect本身。

    11.[交互式的使用expect]

      在前面,我們提到可以通過(guò)interact命令來(lái)交互式的使用腳本。基本上來(lái)說(shuō),interact命令提供了對(duì)對(duì)話的自由訪問(wèn),但我們需要一些更精細(xì)的控制。這一點(diǎn),我們也可以使用expect來(lái)達(dá)到,因?yàn)閑xpect從標(biāo)準(zhǔn)輸入中讀取輸入和從進(jìn)程中讀取輸入一樣的簡(jiǎn)單。 但是,我們要使用expect_user和send_user來(lái)進(jìn)行標(biāo)準(zhǔn)I/O,同時(shí)不改變spawn_id。

      下面的這個(gè)腳本在一定的時(shí)間內(nèi)從標(biāo)準(zhǔn)輸入里面讀取一行。這個(gè)腳本叫做timed_read,可以從csh里面調(diào)用,比如說(shuō),set answer="timed_read 30"就能調(diào)用它。

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

       第三行從用戶那里接收任何以新行符結(jié)束的任何一行。最后一行把它返回給標(biāo)準(zhǔn)輸出。如果在特定的時(shí)間內(nèi)沒(méi)有得到任何鍵入,則返回也為空。

       第一行支持"#!"的系統(tǒng)直接的啟動(dòng)腳本。(如果把腳本的屬性加上可執(zhí)行屬性則不要在腳本前面加上expect)。當(dāng)然了腳本總是可以顯式的用"expect scripot"來(lái)啟動(dòng)。在-c后面的選項(xiàng)在任何腳本語(yǔ)句執(zhí)行前就被執(zhí)行。比如說(shuō),不要修改腳本本身,僅僅在命令行上加上-c "trace...",該腳本可以加上trace功能了(省略號(hào)表示trace的選項(xiàng))。

       在命令行里實(shí)際上可以加上多個(gè)命令,只要中間以";"分開(kāi)就可以了。比如說(shuō),下面這個(gè)命令行:

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

       一旦你把超時(shí)時(shí)限設(shè)置好而且程序啟動(dòng)之后,expect就開(kāi)始等待文件結(jié)束符或者20秒的超時(shí)時(shí)限。 如果遇到了文件結(jié)束符(EOF),該程序就會(huì)停下來(lái),然后expect返回。如果是遇到了超時(shí)的情況,expect就返回。在這兩中情況里面,都隱式的殺死了當(dāng)前進(jìn)程。

       如果我們不使用expect而來(lái)實(shí)現(xiàn)以上兩個(gè)例子的功能的話,我們還是可以學(xué)習(xí)到很多的東西的。在這兩中情況里面,通常的解決方案都是fork另一個(gè)睡眠的子進(jìn)程并且用signal通知原來(lái)的shell。如果這個(gè)過(guò)程或者讀先發(fā)生的話,shell就會(huì)殺司那個(gè)睡眠的進(jìn)程。 傳遞pid和防止后臺(tái)進(jìn)程產(chǎn)生啟動(dòng)信息是一個(gè)讓除了高手級(jí)shell程序員之外的人頭痛的事情。提供一個(gè)通用的方法來(lái)象這樣啟動(dòng)多個(gè)進(jìn)程會(huì)使shell腳本非常的復(fù)雜。 所以幾乎可以肯定的是,程序員一般都用一個(gè)專門C程序來(lái)解決這樣一個(gè)問(wèn)題。

       expect_user,send_user,send_error(向標(biāo)準(zhǔn)錯(cuò)誤終端輸出)在比較長(zhǎng)的,用來(lái)把從進(jìn)程來(lái)的復(fù)雜交互翻譯成簡(jiǎn)單交互的expect腳本里面使用的比較頻繁。在參考[7]里面,Libs描述怎樣用腳本來(lái)安全的包裹(wrap)adb,怎樣把系統(tǒng)管理員從需要掌握adb的細(xì)節(jié)里面解脫出來(lái),同時(shí)大大的降低了由于錯(cuò)誤的擊鍵而導(dǎo)致的系統(tǒng)崩潰。

       一個(gè)簡(jiǎn)單的例子能夠讓ftp自動(dòng)的從一個(gè)私人的帳號(hào)里面取文件。在這種情況里,要求提供密碼。 即使文件的訪問(wèn)是受限的,你也應(yīng)該避免把密碼以明文的方式存儲(chǔ)在文件里面。把密碼作為腳本運(yùn)行時(shí)的參數(shù)也是不合適的,因?yàn)橛胮s命令能看到它們。有一個(gè)解決的方法就是在腳本運(yùn)行的開(kāi)始調(diào)用expect_user來(lái)讓用戶輸入以后可能使用的密碼。這個(gè)密碼必須只能讓這個(gè)腳本知道,即使你是每個(gè)小時(shí)都要重試ftp。

       即使信息是立即輸入進(jìn)去的,這個(gè)技巧也是非常有用。比如說(shuō),你可以寫一個(gè)腳本,把你每一個(gè)主機(jī)上不同的帳號(hào)上的密碼都改掉,不管他們使用的是不是同一個(gè)密碼數(shù)據(jù)庫(kù)。如果你要手工達(dá)到這樣一個(gè)功能的話,你必須Telnet到每一個(gè)主機(jī)上,并且手工輸入新的密碼。而使用expect,你可以只輸入密碼一次而讓腳本來(lái)做其它的事情。

       expect_user和interact也可以在一個(gè)腳本里面混合的使用。考慮一下在調(diào)試一個(gè)程序的循環(huán)時(shí),經(jīng)過(guò)好多步之后才失敗的情況。一個(gè)expect腳本可以驅(qū)動(dòng)哪個(gè)調(diào)試器,設(shè)置好斷點(diǎn),執(zhí)行該程序循環(huán)的若干步,然后將控制返回給鍵盤。它也可以在返回控制之前,在循環(huán)體和條件測(cè)試之間來(lái)回的切換。

    6.[passwd和一致性檢查]

      在前面,我們提到passwd程序在缺乏用戶交互的情況下,不能運(yùn)行,passwd
    會(huì)忽略I/O重定向,也不能嵌入到管道里邊以便能從別的程序或者文件里讀取輸
    入。這個(gè)程序堅(jiān)持要求真正的與用戶進(jìn)行交互。因?yàn)榘踩脑颍琾asswd被設(shè)計(jì)
    成這樣,但結(jié)果導(dǎo)致沒(méi)有非交互式的方法來(lái)檢驗(yàn)passwd。這樣一個(gè)對(duì)系統(tǒng)安全
    至關(guān)重要的程序竟然沒(méi)有辦法進(jìn)行可靠的檢驗(yàn),真實(shí)具有諷刺意味。

      passwd以一個(gè)用戶名作為參數(shù),交互式的提示輸入密碼。下面的expect腳
    本以用戶名和密碼作為參數(shù)而非交互式的運(yùn)行。

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

      第一行以用戶名做參數(shù)啟動(dòng)passwd程序,為方便起見(jiàn),第二行把密碼存到
    一個(gè)變量里面。和shell類似,變量的使用也不需要提前聲明。

      在第三行,expect搜索模式"*password:",其中*允許匹配任意輸入,所
    以對(duì)于避免指定所有細(xì)節(jié)而言是非常有效的。 上面的程序里沒(méi)有action,所以
    expect檢測(cè)到該模式后就繼續(xù)運(yùn)行。

      一旦接收到提示后,下一行就就把密碼送給當(dāng)前進(jìn)程。表明回車。(實(shí)
    際上,所有的C的關(guān)于字符的約定都支持)。上面的程序中有兩個(gè)expect-send
    序列,因?yàn)閜asswd為了對(duì)輸入進(jìn)行確認(rèn),要求進(jìn)行兩次輸入。在非交互式程序
    里面,這是毫無(wú)必要的,但由于假定passwd是在和用戶進(jìn)行交互,所以我們的
    腳本還是這樣做了。

      最后,"expect eof"這一行的作用是在passwd的輸出中搜索文件結(jié)束符,
    這一行語(yǔ)句還展示了關(guān)鍵字的匹配。另外一個(gè)關(guān)鍵字匹配就是timeout了,
    timeout被用于表示所有匹配的失敗而和一段特定長(zhǎng)度的時(shí)間相匹配。在這里
    eof是非常有必要的,因?yàn)閜asswd被設(shè)計(jì)成會(huì)檢查它的所有I/O是否都成功了,
    包括第二次輸入密碼時(shí)產(chǎn)生的最后一個(gè)新行。

      這個(gè)腳本已經(jīng)足夠展示passwd命令的基本交互性。另外一個(gè)更加完備的例
    子回檢查別的一些行為。比如說(shuō),下面的這個(gè)腳本就能檢查passwd程序的別的
    幾個(gè)方面。所有的提示都進(jìn)行了檢查。對(duì)垃圾輸入的檢查也進(jìn)行了適當(dāng)?shù)奶?br />理。進(jìn)程死亡,超乎尋常的慢響應(yīng),或者別的非預(yù)期的行為都進(jìn)行了處理。


        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

       
      這個(gè)腳本退出時(shí)用一個(gè)數(shù)字來(lái)表示所發(fā)生的情況。0表示passwd程序正常
    運(yùn)行,1表示非預(yù)期的死亡,2表示鎖定,等等。使用數(shù)字是為了簡(jiǎn)單起見(jiàn)。
    expect返回字符串和返回?cái)?shù)字是一樣簡(jiǎn)單的,即使是派生程序自身產(chǎn)生的消息
    也是一樣的。實(shí)際上,典型的做法是把整個(gè)交互的過(guò)程存到一個(gè)文件里面,只
    有當(dāng)程序的運(yùn)行和預(yù)期一樣的時(shí)候才把這個(gè)文件刪除。否則這個(gè)log被留待以
    后進(jìn)一步的檢查。

      這個(gè)passwd檢查腳本被設(shè)計(jì)成由別的腳本來(lái)驅(qū)動(dòng)。這第二個(gè)腳本從一個(gè)文
    件里面讀取參數(shù)和預(yù)期的結(jié)果。對(duì)于每一個(gè)輸入?yún)?shù)集,它調(diào)用第一個(gè)腳本并
    且把結(jié)果和預(yù)期的結(jié)果相比較。(因?yàn)檫@個(gè)任務(wù)是非交互的,一個(gè)普通的老式
    shell就可以用來(lái)解釋第二個(gè)腳本)。比如說(shuō),一個(gè)passwd的數(shù)據(jù)文件很有可能
    就象下面一樣。

        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        -

      第一個(gè)域的名字是要被運(yùn)行的回歸腳本。第二個(gè)域是需要和結(jié)果相匹配的
    退出值。第三個(gè)域就是用戶名。第四個(gè)域和第五個(gè)域就是提示時(shí)應(yīng)該輸入的密
    碼。減號(hào)僅僅表示那里有一個(gè)域,這個(gè)域其實(shí)絕對(duì)不會(huì)用到。在第一個(gè)行中
    ,bogus表示用戶名是非法的,因此passwd會(huì)響應(yīng)說(shuō):沒(méi)有此用戶。expect在
    退出時(shí)會(huì)返回3,3恰好就是第二個(gè)域。在最后一行中,^C就是被切實(shí)的送給程
    序來(lái)驗(yàn)證程序是否恰當(dāng)?shù)耐顺觥?br />
      通過(guò)這種方法,expect可以用來(lái)檢驗(yàn)和調(diào)試交互式軟件,這恰恰是IEEE的
    POSIX 1003.2(shell和工具)的一致性檢驗(yàn)所要求的。進(jìn)一步的說(shuō)明請(qǐng)參考
    Libes[6]。

    7.[rogue 和偽終端]

      Unix用戶肯定對(duì)通過(guò)管道來(lái)和其他進(jìn)程相聯(lián)系的方式非常的熟悉(比如說(shuō):
    一個(gè)shell管道)。expect使用偽終端來(lái)和派生的進(jìn)程相聯(lián)系。偽終端提供了終
    端語(yǔ)義以便程序認(rèn)為他們正在和真正的終端進(jìn)行I/O操作。

      比如說(shuō),BSD的探險(xiǎn)游戲rogue在生模式下運(yùn)行,并假定在連接的另一端是
    一個(gè)可尋址的字符終端。可以用expect編程,使得通過(guò)使用用戶界面可以玩這
    個(gè)游戲。

      rogue這個(gè)探險(xiǎn)游戲首先提供給你一個(gè)有各種物理屬性,比如說(shuō)力量值,的
    角色。在大部分時(shí)間里,力量值都是16,但在幾乎每20次里面就會(huì)有一個(gè)力量
    值是18。很多的rogue玩家都知道這一點(diǎn),但沒(méi)有人愿意啟動(dòng)程序20次以獲得一
    個(gè)好的配置。下面的這個(gè)腳本就能達(dá)到這個(gè)目的。

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

      第一行是個(gè)for循環(huán),和C語(yǔ)言的控制格式很象。rogue啟動(dòng)后,expect就
    檢查看力量值是18還是16,如果是16,程序就通過(guò)執(zhí)行close和wait來(lái)退出。
    這兩個(gè)命令的作用分別是關(guān)閉和偽終端的連接和等待進(jìn)程退出。rogue讀到一
    個(gè)文件結(jié)束符就推出,從而循環(huán)繼續(xù)運(yùn)行,產(chǎn)生一個(gè)新的rogue游戲來(lái)檢查。

      當(dāng)一個(gè)值為18的配置找到后,控制就推出循環(huán)并跳到最后一行腳本。
    interact把控制轉(zhuǎn)移給用戶以便他們能夠玩這個(gè)特定的游戲。

      想象一下這個(gè)腳本的運(yùn)行。你所能真正看到的就是20或者30個(gè)初始的配置
    在不到一秒鐘的時(shí)間里掠過(guò)屏幕,最后留給你的就是一個(gè)有著很好配置的游戲
    。唯一比這更好的方法就是使用調(diào)試工具來(lái)玩游戲。

      我們很有必要認(rèn)識(shí)到這樣一點(diǎn):rogue是一個(gè)使用光標(biāo)的圖形游戲。
    expect程序員必須了解到:光標(biāo)的運(yùn)動(dòng)并不一定以一種直觀的方式在屏幕上體
    現(xiàn)。幸運(yùn)的是,在我們這個(gè)例子里,這不是一個(gè)問(wèn)題。將來(lái)的對(duì)expect的改
    進(jìn)可能會(huì)包括一個(gè)內(nèi)嵌的能支持字符圖形區(qū)域的終端模擬器。

    8.[ftp]

      我們使用expect寫第一個(gè)腳本并沒(méi)有打印出"Hello,World"。實(shí)際上,它
    實(shí)現(xiàn)了一些更有用的功能。它能通過(guò)非交互的方式來(lái)運(yùn)行ftp。ftp是用來(lái)在支
    持TCP/IP的網(wǎng)絡(luò)上進(jìn)行文件傳輸?shù)某绦颉3艘恍┖?jiǎn)單的功能,一般的實(shí)現(xiàn)都
    要求用戶的參與。

      下面這個(gè)腳本從一個(gè)主機(jī)上使用匿名ftp取下一個(gè)文件來(lái)。其中,主機(jī)名
    是第一個(gè)參數(shù)。文件名是第二個(gè)參數(shù)。

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

      上面這個(gè)程序被設(shè)計(jì)成在后臺(tái)進(jìn)行ftp。雖然他們?cè)诘讓邮褂煤蚭xpect類
    似的機(jī)制,但他們的可編程能力留待改進(jìn)。因?yàn)閑xpect提供了高級(jí)語(yǔ)言,你可
    以對(duì)它進(jìn)行修改來(lái)滿足你的特定需求。比如說(shuō),你可以加上以下功能:

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

      expect還可以執(zhí)行其他的更復(fù)雜的任務(wù)。比如說(shuō),他可以使用McGill大學(xué)
    的Archie系統(tǒng)。Archie是一個(gè)匿名的Telnet服務(wù),它提供對(duì)描述Internet上可
    通過(guò)匿名ftp獲取的文件的數(shù)據(jù)庫(kù)的訪問(wèn)。通過(guò)使用這個(gè)服務(wù),腳本可以詢問(wèn)
    Archie某個(gè)特定的文件的位置,并把它從ftp服務(wù)器上取下來(lái)。這個(gè)功能的實(shí)
    現(xiàn)只要求在上面那個(gè)腳本中加上幾行就可以。

      現(xiàn)在還沒(méi)有什么已知的后臺(tái)-ftp能夠?qū)崿F(xiàn)上面的幾項(xiàng)功能,能不要說(shuō)所有
    的功能了。在expect里面,它的實(shí)現(xiàn)卻是非常的簡(jiǎn)單。“堅(jiān)持”的實(shí)現(xiàn)只要求
    在expect腳本里面加上一個(gè)循環(huán)。“通知”的實(shí)現(xiàn)只要執(zhí)行mail和write就可以
    了。“初始化文件”的實(shí)現(xiàn)可以使用一個(gè)命令,source .ftprc,就可以了,
    在.ftprc里面可以有任何的expect命令。

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

    9.[fsck]

      fsck是另外一個(gè)缺乏足夠的用戶接口的例子。fsck幾乎沒(méi)有提供什么方法
    來(lái)預(yù)先的回答一些問(wèn)題。你能做的就是給所有的問(wèn)題都回答"yes"或者都回答
    "no"。

      下面的程序段展示了一個(gè)腳本如何的使的自動(dòng)的對(duì)某些問(wèn)題回答"yes",
    而對(duì)某些問(wèn)題回答"no"。下面的這個(gè)腳本一開(kāi)始先派生fsck進(jìn)程,然后對(duì)其
    中兩種類型的問(wèn)題回答"yes",而對(duì)其他的問(wèn)題回答"no"。

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

      在下面這個(gè)版本里面,兩個(gè)問(wèn)題的回答是不同的。而且,如果腳本遇到
    了什么它不能理解的東西,就會(huì)執(zhí)行interact命令把控制交給用戶。用戶的
    擊鍵直接交給fsck處理。當(dāng)執(zhí)行完后,用戶可以通過(guò)按"+"鍵來(lái)退出或者把
    控制交還給expect。如果控制是交還給腳本了,腳本就會(huì)自動(dòng)的控制進(jìn)程的
    剩余部分的運(yùn)行。

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

      如果沒(méi)有expect,fsck只有在犧牲一定功能的情況下才可以非交互式的
    運(yùn)行。fsck幾乎是不可編程的,但它卻是系統(tǒng)管理的最重要的工具。許多別
    的工具的用戶接口也一樣的不足。實(shí)際上,正是其中的一些程序的不足導(dǎo)致
    了expect的誕生。

    10.[控制多個(gè)進(jìn)程:作業(yè)控制]


      expect的作業(yè)控制概念精巧的避免了通常的實(shí)現(xiàn)困難。其中包括了兩個(gè)問(wèn)
    題:一個(gè)是expect如何處理經(jīng)典的作業(yè)控制,即當(dāng)你在終端上按下^Z鍵時(shí)
    expect如何處理;另外一個(gè)就是expect是如何處理多進(jìn)程的。

      對(duì)第一個(gè)問(wèn)題的處理是:忽略它。expect對(duì)經(jīng)典的作業(yè)控制一無(wú)所知。比
    如說(shuō),你派生了一個(gè)程序并且發(fā)送一個(gè)^Z給它,它就會(huì)停下來(lái)(這是偽終端的
    完美之處)而expect就會(huì)永遠(yuǎn)的等下去。

      但是,實(shí)際上,這根本就不成一個(gè)問(wèn)題。對(duì)于一個(gè)expect腳本,沒(méi)有必要
    向進(jìn)程發(fā)送^Z。也就是說(shuō),沒(méi)有必要停下一個(gè)進(jìn)程來(lái)。expect僅僅是忽略了
    一個(gè)進(jìn)程,而把自己的注意力轉(zhuǎn)移到其他的地方。這就是expect的作業(yè)控制
    思想,這個(gè)思想也一直工作的很好。

      從用戶的角度來(lái)看是象這樣的:當(dāng)一個(gè)進(jìn)程通過(guò)spawn命令啟動(dòng)時(shí),變量
    spawn_id就被設(shè)置成某進(jìn)程的描述符。由spawn_id描述的進(jìn)程就被認(rèn)為是當(dāng)
    前進(jìn)程。(這個(gè)描述符恰恰就是偽終端文件的描述符,雖然用戶把它當(dāng)作一個(gè)
    不透明的物體)。expect和send命令僅僅和當(dāng)前進(jìn)程進(jìn)行交互。所以,切換一
    個(gè)作業(yè)所需要做的僅僅是把該進(jìn)程的描述符賦給spawn_id。

      這兒有一個(gè)例子向我們展示了如何通過(guò)作業(yè)控制來(lái)使兩個(gè)chess進(jìn)程進(jìn)行
    交互。在派生完兩個(gè)進(jìn)程之后,一個(gè)進(jìn)程被通知先動(dòng)一步。在下面的循環(huán)里
    面,每一步動(dòng)作都送給另外一個(gè)進(jìn)程。其中,read_move和write_move兩個(gè)過(guò)
    程留給讀者來(lái)實(shí)現(xiàn)。(實(shí)際上,它們的實(shí)現(xiàn)非常的容易,但是,由于太長(zhǎng)了所
    以沒(méi)有包含在這里)。

        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
        }

       有一些應(yīng)用程序和chess程序不太一樣,在chess程序里,的兩個(gè)玩家
    輪流動(dòng)。下面這個(gè)腳本實(shí)現(xiàn)了一個(gè)冒充程序。它能夠控制一個(gè)終端以便用戶
    能夠登錄和正常的工作。但是,一旦系統(tǒng)提示輸入密碼或者輸入用戶名的時(shí)
    候,expect就開(kāi)始把擊鍵記下來(lái),一直到用戶按下回車鍵。這有效的收集了
    用戶的密碼和用戶名,還避免了普通的冒充程序的"Incorrect password-try
    again"。而且,如果用戶連接到另外一個(gè)主機(jī)上,那些額外的登錄也會(huì)被
    記錄下來(lái)。

        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
            }
        }
            

       這個(gè)腳本是這樣工作的。首先連接到一個(gè)login進(jìn)程和終端。缺省的,
    所有的對(duì)話都記錄到標(biāo)準(zhǔn)輸出上(通過(guò)send_user)。因?yàn)槲覀儗?duì)此并不感興趣,
    所以,我們通過(guò)命令"log_user 0"來(lái)禁止這個(gè)功能。(有很多的命令來(lái)控制
    可以看見(jiàn)或者可以記錄的東西)。

       在循環(huán)里面,select等待終端或者login進(jìn)程上的動(dòng)作,并且返回一個(gè)
    等待輸入的spawn_id表。如果在表里面找到了一個(gè)值的話,case就執(zhí)行一個(gè)
    action。比如說(shuō),如果字符串"login"出現(xiàn)在login進(jìn)程的輸出中,提示就會(huì)
    被記錄到標(biāo)準(zhǔn)輸出上,并且有一個(gè)標(biāo)志被設(shè)置以便通知腳本開(kāi)始記錄用戶的
    擊鍵,直至用戶按下了回車鍵。無(wú)論收到什么,都會(huì)回顯到終端上,一個(gè)相
    應(yīng)的action會(huì)在腳本的終端那一部分執(zhí)行。

       這些例子顯示了expect的作業(yè)控制方式。通過(guò)把自己插入到對(duì)話里面,
    expect可以在進(jìn)程之間創(chuàng)建復(fù)雜的I/O流。可以創(chuàng)建多扇出,復(fù)用扇入的,
    動(dòng)態(tài)的數(shù)據(jù)相關(guān)的進(jìn)程圖。

       相比之下,shell使得它自己一次一行的讀取一個(gè)文件顯的很困難。
    shell強(qiáng)迫用戶按下控制鍵(比如,^C,^Z)和關(guān)鍵字(比如fg和bg)來(lái)實(shí)現(xiàn)作業(yè)的
    切換。這些都無(wú)法從腳本里面利用。相似的是:以非交互方式運(yùn)行的shell并
    不處理“歷史記錄”和其他一些僅僅為交互式使用設(shè)計(jì)的特征。這也出現(xiàn)了和
    前面哪個(gè)passwd程序的相似問(wèn)題。相似的,也無(wú)法編寫能夠回歸的測(cè)試shell
    的某些動(dòng)作的shell腳本。結(jié)果導(dǎo)致shell的這些方面無(wú)法進(jìn)行徹底的測(cè)試。

       如果使用expect的話,可以使用它的交互式的作業(yè)控制來(lái)驅(qū)動(dòng)shell。一
    個(gè)派生的shell認(rèn)為它是在交互的運(yùn)行著,所以會(huì)正常的處理作業(yè)控制。它不
    僅能夠解決檢驗(yàn)處理作業(yè)控制的shell和其他一些程序的問(wèn)題。還能夠在必要
    的時(shí)候,讓shell代替expect來(lái)處理作業(yè)。可以支持使用shell風(fēng)格的作業(yè)控
    制來(lái)支持進(jìn)程的運(yùn)行。這意味著:首先派生一個(gè)shell,然后把命令送給shell
    來(lái)啟動(dòng)進(jìn)程。如果進(jìn)程被掛起,比如說(shuō),發(fā)送了一個(gè)^Z,進(jìn)程就會(huì)停下來(lái),并
    把控制返回給shell。對(duì)于expect而言,它還在處理同一個(gè)進(jìn)程(原來(lái)那個(gè)
    shell)。

      expect的解決方法不僅具有很大的靈活性,它還避免了重復(fù)已經(jīng)存在于
    shell中的作業(yè)控制軟件。通過(guò)使用shell,由于你可以選擇你想派生的shell,
    所以你可以根據(jù)需要獲得作業(yè)控制權(quán)。而且,一旦你需要(比如說(shuō)檢驗(yàn)的時(shí)
    候),你就可以驅(qū)動(dòng)一個(gè)shell來(lái)讓這個(gè)shell以為它正在交互式的運(yùn)行。這一
    點(diǎn)對(duì)于在檢測(cè)到它們是否在交互式的運(yùn)行之后會(huì)改變輸出的緩沖的程序來(lái)說(shuō)也
    是很重要的。

      為了進(jìn)一步的控制,在interact執(zhí)行期間,expect把控制終端(是啟動(dòng)
    expect的那個(gè)終端,而不是偽終端)設(shè)置成生模式以便字符能夠正確的傳送給
    派生的進(jìn)程。當(dāng)expect在沒(méi)有執(zhí)行interact的時(shí)候,終端處于熟模式下,這時(shí)
    候作業(yè)控制就可以作用于expect本身。

    11.[交互式的使用expect]

      在前面,我們提到可以通過(guò)interact命令來(lái)交互式的使用腳本。基本上
    來(lái)說(shuō),interact命令提供了對(duì)對(duì)話的自由訪問(wèn),但我們需要一些更精細(xì)的控
    制。這一點(diǎn),我們也可以使用expect來(lái)達(dá)到,因?yàn)閑xpect從標(biāo)準(zhǔn)輸入中讀取
    輸入和從進(jìn)程中讀取輸入一樣的簡(jiǎn)單。 但是,我們要使用expect_user和
    send_user來(lái)進(jìn)行標(biāo)準(zhǔn)I/O,同時(shí)不改變spawn_id。

      下面的這個(gè)腳本在一定的時(shí)間內(nèi)從標(biāo)準(zhǔn)輸入里面讀取一行。這個(gè)腳本叫
    做timed_read,可以從csh里面調(diào)用,比如說(shuō),set answer="timed_read 30"
    就能調(diào)用它。

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

       第三行從用戶那里接收任何以新行符結(jié)束的任何一行。最后一行把它
    返回給標(biāo)準(zhǔn)輸出。如果在特定的時(shí)間內(nèi)沒(méi)有得到任何鍵入,則返回也為空。

       第一行支持"#!"的系統(tǒng)直接的啟動(dòng)腳本。(如果把腳本的屬性加上可執(zhí)
    行屬性則不要在腳本前面加上expect)。當(dāng)然了腳本總是可以顯式的用
    "expect scripot"來(lái)啟動(dòng)。在-c后面的選項(xiàng)在任何腳本語(yǔ)句執(zhí)行前就被執(zhí)行。
    比如說(shuō),不要修改腳本本身,僅僅在命令行上加上-c "trace...",該腳本可
    以加上trace功能了(省略號(hào)表示trace的選項(xiàng))。

       在命令行里實(shí)際上可以加上多個(gè)命令,只要中間以";"分開(kāi)就可以了。
    比如說(shuō),下面這個(gè)命令行:

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

       一旦你把超時(shí)時(shí)限設(shè)置好而且程序啟動(dòng)之后,expect就開(kāi)始等待文件
    結(jié)束符或者20秒的超時(shí)時(shí)限。 如果遇到了文件結(jié)束符(EOF),該程序就會(huì)停
    下來(lái),然后expect返回。如果是遇到了超時(shí)的情況,expect就返回。在這兩
    中情況里面,都隱式的殺死了當(dāng)前進(jìn)程。

       如果我們不使用expect而來(lái)實(shí)現(xiàn)以上兩個(gè)例子的功能的話,我們還是可
    以學(xué)習(xí)到很多的東西的。在這兩中情況里面,通常的解決方案都是fork另一個(gè)
    睡眠的子進(jìn)程并且用signal通知原來(lái)的shell。如果這個(gè)過(guò)程或者讀先發(fā)生的
    話,shell就會(huì)殺司那個(gè)睡眠的進(jìn)程。 傳遞pid和防止后臺(tái)進(jìn)程產(chǎn)生啟動(dòng)信息
    是一個(gè)讓除了高手級(jí)shell程序員之外的人頭痛的事情。提供一個(gè)通用的方法
    來(lái)象這樣啟動(dòng)多個(gè)進(jìn)程會(huì)使shell腳本非常的復(fù)雜。 所以幾乎可以肯定的是,
    程序員一般都用一個(gè)專門C程序來(lái)解決這樣一個(gè)問(wèn)題。

       expect_user,send_user,send_error(向標(biāo)準(zhǔn)錯(cuò)誤終端輸出)在比較長(zhǎng)
    的,用來(lái)把從進(jìn)程來(lái)的復(fù)雜交互翻譯成簡(jiǎn)單交互的expect腳本里面使用的比較
    頻繁。在參考[7]里面,Libs描述怎樣用腳本來(lái)安全的包裹(wrap)adb,怎樣
    把系統(tǒng)管理員從需要掌握adb的細(xì)節(jié)里面解脫出來(lái),同時(shí)大大的降低了由于錯(cuò)
    誤的擊鍵而導(dǎo)致的系統(tǒng)崩潰。

       一個(gè)簡(jiǎn)單的例子能夠讓ftp自動(dòng)的從一個(gè)私人的帳號(hào)里面取文件。在這
    種情況里,要求提供密碼。 即使文件的訪問(wèn)是受限的,你也應(yīng)該避免把密碼
    以明文的方式存儲(chǔ)在文件里面。把密碼作為腳本運(yùn)行時(shí)的參數(shù)也是不合適的,
    因?yàn)橛胮s命令能看到它們。有一個(gè)解決的方法就是在腳本運(yùn)行的開(kāi)始調(diào)用
    expect_user來(lái)讓用戶輸入以后可能使用的密碼。這個(gè)密碼必須只能讓這個(gè)腳
    本知道,即使你是每個(gè)小時(shí)都要重試ftp。

       即使信息是立即輸入進(jìn)去的,這個(gè)技巧也是非常有用。比如說(shuō),你可
    以寫一個(gè)腳本,把你每一個(gè)主機(jī)上不同的帳號(hào)上的密碼都改掉,不管他們使用
    的是不是同一個(gè)密碼數(shù)據(jù)庫(kù)。如果你要手工達(dá)到這樣一個(gè)功能的話,你必須
    Telnet到每一個(gè)主機(jī)上,并且手工輸入新的密碼。而使用expect,你可以只輸
    入密碼一次而讓腳本來(lái)做其它的事情。

       expect_user和interact也可以在一個(gè)腳本里面混合的使用。考慮一下
    在調(diào)試一個(gè)程序的循環(huán)時(shí),經(jīng)過(guò)好多步之后才失敗的情況。一個(gè)expect腳本
    可以驅(qū)動(dòng)哪個(gè)調(diào)試器,設(shè)置好斷點(diǎn),執(zhí)行該程序循環(huán)的若干步,然后將控制
    返回給鍵盤。它也可以在返回控制之前,在循環(huán)體和條件測(cè)試之間來(lái)回的切
    換。

    posted on 2006-03-25 23:24 weidagang2046 閱讀(275) 評(píng)論(0)  編輯  收藏 所屬分類: Perl

    主站蜘蛛池模板: 亚洲春色在线观看| 久久精品国产亚洲AV麻豆~| 亚洲国产成人资源在线软件| 免费观看激色视频网站(性色)| 久久精品蜜芽亚洲国产AV| 91免费国产精品| 亚洲午夜精品一区二区公牛电影院| 99热在线精品免费播放6| 99久久亚洲精品无码毛片| 57pao一国产成视频永久免费| 亚洲春色另类小说| 免费精品国产自产拍在| 亚洲av无码成人影院一区| 免费国产真实迷j在线观看| jizz免费观看| 亚洲AV日韩AV永久无码免下载 | 日韩电影免费在线| 日韩色视频一区二区三区亚洲| 国产乱子伦精品免费无码专区| 成年大片免费视频播放一级| 在线A亚洲老鸭窝天堂| 免费女人高潮流视频在线观看| 亚洲第一区视频在线观看| 午夜视频免费观看| 国产免费高清69式视频在线观看| 久久国产亚洲观看| 成全影视免费观看大全二| 一本到卡二卡三卡免费高| 亚洲天堂中文字幕| 国产精品高清全国免费观看| 成人无码a级毛片免费| 国产精品亚洲精品| 亚洲一区日韩高清中文字幕亚洲| 无码少妇精品一区二区免费动态 | 在线观看亚洲网站| 亚洲一区二区三区偷拍女厕| 曰批全过程免费视频播放网站 | 免费中文字幕不卡视频| 国内精品免费在线观看| 亚洲欧洲无码AV不卡在线| 国产亚洲精品影视在线产品|