日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

Expect 教程中文版

發(fā)布時間:2023/11/27 生活经验 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Expect 教程中文版 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

原文鏈接

本教程由*葫蘆娃*翻譯,并做了適當(dāng)?shù)男薷?#xff0c;可以自由的用于非商業(yè)目的。?

[BUG]?

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

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

[目錄]?
  ?
  1.摘要?
  2.關(guān)鍵字?
  3.簡介?
  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.參考書籍?

1.[摘要]?

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

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

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

  “對于fsck的所有問題都假定一個“yes”響應(yīng);在這樣使用的時候,必須特別的小心,因為它實際上允許程序無條件的繼續(xù)運行,即使是遇到了一些非常嚴(yán)重的錯誤”?
  ?
  相比之下,-n選項就安全的多,但它實際上幾乎一點用都沒有。這種接口非常的糟糕,但是卻有許多的程序都是這種風(fēng)格?!∥募鬏敵绦騠tp有一個選項可以禁止交互式的提問,以便能從一個腳本里面運行。但一旦發(fā)生了錯誤,它沒有提供的處理措施。?

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

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

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


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

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

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

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

      expect patlist1 action1 patlist2 action2.....?

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

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

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

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

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

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

5.[callback]?

  令人非常驚訝的是,一些小的腳本如何的產(chǎn)生一些有用的功能。下面是一個撥電話號碼的腳本。他用來把收費反向,以便使得長途電話對計算機(jī)計費。這個腳本 用類似“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*"?

  第一行是注釋,第二行展示了如何調(diào)用沒有交互的Unix程序。sleep 4會使程序阻塞4秒,以使得用戶有時間來退出,因為modem總是會回叫用戶已經(jīng)使用的電話號碼。?

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

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

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

6.[passwd和一致性檢查]?

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

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

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

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

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

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

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

  這個腳本已經(jīng)足夠展示passwd命令的基本交互性。另外一個更加完備的例子回檢查別的一些行為。比如說,下面的這個腳本就能檢查passwd程序的 別的幾個方面。所有的提示都進(jìn)行了檢查。對垃圾輸入的檢查也進(jìn)行了適當(dāng)?shù)奶幚?。進(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?

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

  這個passwd檢查腳本被設(shè)計成由別的腳本來驅(qū)動。這第二個腳本從一個文件里面讀取參數(shù)和預(yù)期的結(jié)果。對于每一個輸入?yún)?shù)集,它調(diào)用第一個腳本并且 把結(jié)果和預(yù)期的結(jié)果相比較。(因為這個任務(wù)是非交互的,一個普通的老式shell就可以用來解釋第二個腳本)。比如說,一個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        -?

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

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

7.[rogue 和偽終端]?

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

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

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

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

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

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

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

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


8.[ftp]?

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

  下面這個腳本從一個主機(jī)上使用匿名ftp取下一個文件來。其中,主機(jī)名是第一個參數(shù)。文件名是第二個參數(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>*"?

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

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

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

  現(xiàn)在還沒有什么已知的后臺-ftp能夠?qū)崿F(xiàn)上面的幾項功能,能不要說所有的功能了。在expect里面,它的實現(xiàn)卻是非常的簡單。“堅持”的實現(xiàn)只要 求在expect腳本里面加上一個循環(huán)?!巴ㄖ钡膶崿F(xiàn)只要執(zhí)行mail和write就可以了?!俺跏蓟募钡膶崿F(xiàn)可以使用一個命令,source .ftprc,就可以了,在.ftprc里面可以有任何的expect命令。?

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

9.[fsck]?

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

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

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

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

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

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

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


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

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

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

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

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

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

    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進(jìn)程和終端。缺省的,所有的對話都記錄到標(biāo)準(zhǔn)輸出上(通過send_user)。因為我們對此并不感興 趣,所以,我們通過命令"log_user 0"來禁止這個功能。(有很多的命令來控制可以看見或者可以記錄的東西)。?

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

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

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

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

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

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

11.[交互式的使用expect]?

  在前面,我們提到可以通過interact命令來交互式的使用腳本?;旧蟻碚f,interact命令提供了對對話的自由訪問,但我們需要一些更精細(xì) 的控制。這一點,我們也可以使用expect來達(dá)到,因為expect從標(biāo)準(zhǔn)輸入中讀取輸入和從進(jìn)程中讀取輸入一樣的簡單。 但是,我們要使用expect_user和send_user來進(jìn)行標(biāo)準(zhǔn)I/O,同時不改變spawn_id。?

  下面的這個腳本在一定的時間內(nèi)從標(biāo)準(zhǔn)輸入里面讀取一行。這個腳本叫做timed_read,可以從csh里面調(diào)用,比如說,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)輸出。如果在特定的時間內(nèi)沒有得到任何鍵入,則返回也為空。?

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

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

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

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

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

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

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

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

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


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

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

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

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

Expect是不斷發(fā)展的,隨著時間的流逝,其功能越來越強(qiáng)大,已經(jīng)成為系統(tǒng)管理員的的一個強(qiáng)大助手。Expect需要Tcl編程語言的支持,要在系統(tǒng)上運行Expect必須首先安裝Tcl。

Expect工作原理

從最簡單的層次來說,Expect的工作方式象一個通用化的Chat腳本工具。Chat腳本最早用于UUCP網(wǎng)絡(luò)內(nèi),以用來實現(xiàn)計算機(jī)之間需要建立連接時進(jìn)行特定的登錄會話的自動化。

Chat腳本由一系列expect-send對組成:expect等待輸出中輸出特定的字符,通常是一個提示符,然后發(fā)送特定的響應(yīng)。例如下面的Chat 腳本實現(xiàn)等待標(biāo)準(zhǔn)輸出出現(xiàn)Login:字符串,然后發(fā)送somebody作為用戶名;然后等待Password:提示符,并發(fā)出響應(yīng)sillyme。


Login: somebody Password: sillyme


這個腳本用來實現(xiàn)一個登錄過程,并用特定的用戶名和密碼實現(xiàn)登錄。?

Expect最簡單的腳本操作模式本質(zhì)上和Chat腳本工作模式是一樣的。下面我們分析一個響應(yīng)chsh命令的腳本。我們首先回顧一下這個交互命令的格式。假設(shè)我們?yōu)橛脩鬰havez改變登錄腳本,命令交互過程如下:?

# 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。

下面是一個能用來實現(xiàn)自動執(zhí)行該命令的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程序的特性。和其他腳本一樣首行指定用來執(zhí)行該腳本的命令程序,這里是/usr/bin/expect。程序第一行用來獲得腳本的執(zhí)行參數(shù)(其保存在數(shù)組$argv中,從0號開始是參數(shù)),并將其保存到變量user中。

第二個參數(shù)使用Expect的spawn命令來啟動腳本和命令的會話,這里啟動的是chsh命令,實際上命令是以衍生子進(jìn)程的方式來運行的。

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

決定如何響應(yīng)

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

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

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

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

使用超時

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

#!/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]
腳本的第一部分首先是得到運行參數(shù)并將其保存到內(nèi)部變量中。?

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]

這是腳本其余的內(nèi)容??梢钥吹絪end_tty命令用來實現(xiàn)在終端上顯示提示符字串和一個冒號及空格。set timeout命令設(shè)置后面所有的expect命令的等待響應(yīng)的超時時間為$tout(-l參數(shù)用來關(guān)閉任何超時設(shè)置)。

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

然后,如果response中內(nèi)容為空則將response值置為默認(rèn)值(如果用戶在超時以后沒有輸入或者用戶僅僅輸入了回車符)。最后send命令將response變量的值加上回車符發(fā)送給標(biāo)準(zhǔn)輸出。

一個有趣的事情是該腳本沒有使用spawn命令。 該expect腳本會與任何調(diào)用該腳本的進(jìn)程交互。

如果該腳本名為prompt,那么它可以用在任何C風(fēng)格的shell中。


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

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

Answer was "silence"

一個更復(fù)雜的例子

下面我們將討論一個更加復(fù)雜的expect腳本例子,這個腳本使用了一些更復(fù)雜的控制結(jié)構(gòu)和很多復(fù)雜的交互過程。這個例子用來實現(xiàn)發(fā)送write命令給任意的用戶,發(fā)送的消息來自于一個文件或者來自于鍵盤輸入。?

#!/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命令用來顯示使用幫助信息到父進(jìn)程(一般為用戶的shell)的標(biāo)準(zhǔn)輸出。?

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

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

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

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

可以看到這里使用了if命令來實現(xiàn)邏輯判斷功能。該命令后面直接跟判斷條件,并且執(zhí)行在判斷條件后的{}內(nèi)的命令。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進(jìn)程實現(xiàn)向用戶發(fā)送消息。這里使用了for命令來實現(xiàn)循環(huán)控制功能,循環(huán)變量首先設(shè)置為1,然后因此遞增。 循環(huán)體是最后的{}的內(nèi)容。這里我們是用腳本的第二個和隨后的參數(shù)來spawn一個write命令,并將每個參數(shù)作為發(fā)送消息的用戶名。lappend命 令使用保存每個spawn的進(jìn)程的進(jìn)程ID號的內(nèi)部變量$spawn_id在變量procs中構(gòu)造了一個進(jìn)程ID號列表。?

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

最后腳本根據(jù)變量nofile的值實現(xiàn)打開消息文件或者提示用戶輸入要發(fā)送的消息。?

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
上面這段代碼說明了實際的消息文本是如何通過無限循環(huán)while被發(fā)送的。while循環(huán)中的 if判斷消息是如何得到的。在非交互模式下,下一行內(nèi)容從消息文件中讀出,當(dāng)文件內(nèi)容結(jié)束時while循環(huán)也就結(jié)束了。(break命令實現(xiàn)終止循環(huán)) 。?

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

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

參考資源

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

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

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

一些有用的expect腳本

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

kibitz:這是一個非常有用的工具。通過它兩個或更多的用戶可以連接到同一個shell進(jìn)程??梢杂糜诩夹g(shù)支持或者培訓(xùn)(參見下圖)。



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

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

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

Expect 超出預(yù)期

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

您是一名“系統(tǒng)程序員”― 您編寫代碼以保持服務(wù)器正常運轉(zhuǎn),并且為您的應(yīng)用程序開發(fā)人員同事提供所需的底層功能。您從哪里獲取所需的信息呢?大多數(shù)編程參考大全關(guān)心客戶機(jī)或者“應(yīng)用程序”問題,而管理書籍通常回避編程而致力于“配置”。

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

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

在 Tcl 上構(gòu)建

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

Tcl 是 Expect 的基礎(chǔ)。任何 Tcl 程序都自動是 Expect 程序。因為有下面兩個原因,所以強(qiáng)調(diào)這一點很重要:

  • 許多人只知道 Expect 是一種“工具”,而從不了解它是一種完全成熟的編程語言。
  • 1994 年,許多真正認(rèn)識到 Expect 的通用能力的程序員都被它迷住了。

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

這里的問題是,過去八年以來,Expect 的底層 Tcl 基礎(chǔ)已經(jīng)有了極大發(fā)展。最初編寫 Expect 時,Tcl 并不追求成為通用的編程語言。從那時起,Tcl 已經(jīng):

  • 知道如何處理完整的八位數(shù)據(jù),甚至能方便地處理 Unicode;
  • 添加了方便的 TCP/IP 抽象;
  • 獲取了數(shù)據(jù)和時間計算以及格式化方面的能力;
  • 改進(jìn)并合理化了其字符串處理;

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

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

?



回頁首



難題的獨特解決方案

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

  • 擴(kuò)展的調(diào)試選項
  • 描述面向字符對話框的便利命令
  • 棘手的面向字符終端的獨一無二的管理

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

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

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

如 Libes 所說,更關(guān)鍵的是:“最終,Expect 是為處理蹩腳的界面而設(shè)計的工具?!碧貏e是 Expect 具有管理抵制 I/O 重定向的應(yīng)用程序的能力。典型示例是命令行?passwd?程序。每個負(fù)責(zé)管理服務(wù)器的人員遲早都需要使密碼更新自動化。第一次嘗試可能是作為 root 用戶運行類似下面的代碼:


失敗的 passwd 自動化

 
passwd $user << HERE
$newpassword
$newpassword
HERE

?

正如每個嘗試它的人很快會發(fā)現(xiàn),這根本不起作用。shell 的 < 和 << 重定向?qū)τ谙?passwd?這樣的程序不起作用。

但是,Expect 可以使重定向起作用。Expect 知道如何與所有面向字符的應(yīng)用程序?qū)υ?#xff0c;即使是象passwd?那樣操縱終端設(shè)置的應(yīng)用程序。

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

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

?



回頁首



有關(guān) Expect 的所有其它須知信息

您還應(yīng)該了解有關(guān) Expect 的其它信息。本專欄的最后部分包括對 Expect 局限的說明、對解決常見問題的 Expect 工作代碼的概述以及可以引導(dǎo)您更深入了解 Expect 編程的參考。

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

Expect 的著名用法是用于測試。Expect 是用于幾個高端產(chǎn)品(包括 gcc)質(zhì)量控制中使用的 DejaGnu 系統(tǒng)的基礎(chǔ)。然而,雖然 Expect 可用于構(gòu)建 GUI,并且在幾個測試框架中也很關(guān)鍵,但是通常 Expect 在用于 GUI 系統(tǒng)的測試框架中?起作用。

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

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

您已經(jīng)知道簡單 I/O 重定向?qū)?passwd?不起作用。何種 Expect 程序提供了更好的結(jié)果呢?


更新密碼的簡單 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 用來使程序自動化所需的全部代碼,其它語言幾乎不可能做到。再多用幾行,您可以一次對成百上千用戶進(jìn)行批處理更新。這是一種常見需求;我經(jīng)常被請去恢復(fù)密碼文件被嚴(yán)重毀壞的服務(wù)器,這里說明了一種開始的方法:


簡單迭代

  
...
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 外觀也只需要多加幾行。假定您想為一名非程序員提供方便地更新密碼的應(yīng)用程序。同樣忽略安全性考慮事項,這就象完成下列代碼一樣簡單:


簡單迭代


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

?

這個小工作應(yīng)用程序具有下面的視覺外觀:


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



回頁首



結(jié)束語

Expect 具有系統(tǒng)程序員通常需要的獨特能力。同時,Expect 是出色的通用編程語言,它在聯(lián)網(wǎng)和 GUI 構(gòu)造方面具有優(yōu)勢。如果您必須只選擇一種用于日常工作的語言,Expect 近乎是理想選擇。

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

?

關(guān)于作者

?

?

Cameron Laird 是 Phaseit, Inc 的一名全職顧問。他經(jīng)常就開放源碼和其它技術(shù)主題撰寫文章并發(fā)表演說。Cameron 感謝 SuSE 的 Reinhard Max 在他準(zhǔn)備這個專欄期間所提供的幫助??梢酝ㄟ^claird@phaseit.net與 Cameron 聯(lián)系。

轉(zhuǎn)載于:https://www.cnblogs.com/wangkangluo1/archive/2012/06/14/2548685.html

總結(jié)

以上是生活随笔為你收集整理的Expect 教程中文版的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。