Expect 手冊中文版

EXPECT(1)

名字:

Expect-----能與交互式程序進行“可程序化”會話的腳本語言

大綱:

(命令選項概述)

expect [ -dDinN ] [ -c cmds ] [-[f|b] ] cmdfile ] [ args ]

概述:

Expect是一種能夠按照腳本內容裏面設定的方式與交互式程序進行“會話”的程序。根據腳本內容,Expect可以知道程序會提示或反饋什麼內容以及什麼是正確的應答。它是一種可以提供“分支和嵌套結構”來引導程序流程的解釋型腳本語言。另外,還可以在隨時需要的時候把控制權交給用戶,然後再還給腳本。
ExpectKExpectTk的混合體。它就按照ExpectTk的方式運行。Expect也可以直接嵌入到C或是C++程序中(這種情況是不涉及Tcl解釋)。請看libexpect(3).
Expect的名字是從被UUCP(UNIXUNIX的拷貝)Kermit(一種文件傳輸協議,由哥倫比亞大學設計)和一些其他Modem控制等程序設計思維大衆化的“send/expect”時序理念中得出的。不像UUCPExpect已經被廣泛應用於很多你可以想像的到的程序和任務當中了。

Expect還能同時和多個程序交互。

例如:下面是一些Expect可以做到的事情

  1. 讓你的計算機呼叫你,這樣你可以不用付呼叫費。

  2. 啓動一個遊戲(例如:rogue),如果不是最佳配置,則一直重新啓動,直到是最佳配置爲止。然後把控制權轉交給你。

  3. 運行fsck的時候,用”yes”或是”no”來回應fsck的交互問題。在沒有預設答案標準的情況下把控制權返還給你。

  4. 連接到另一個網絡或是BBS站點,自動收取你的郵件,就像郵件是發往你的當地系統一樣。

  5. 在執行rlogin,telnet,tip,su,chgrp等等命令的時候保存“環境變量”,“當前目錄”或是其他一些信息。

有很多原因致使Shell不能完成這樣的任務(你自己可以試試看)。而這一切對於Expect

來說都是可以的。一般情況下,當一個程序需要程序與用戶進行交互的時候就需要用到Expect。還需要的一個前提是這種交互必須能程序化(例如:循環結構,選擇結構等等,個人認爲必須是有規律可循的)。如果需要的話,Expect還能把控制權返還給用戶。同樣,用戶也可以在任何時候把控制權還給腳本程序。

 

用法:

Expect通過讀取cmdfile(命令文件)來執行一系列指令。只要系統支持“#!“,在Script腳本文件的首行標明“#!/usr/local/bin/expect–f“,並賦予腳本文件可執行權限,執行腳本文件就可以(隱含方式或是默認)調用Expect

當然,上面的路徑必須正確地指明Expect解釋程序的位置。/usr/local/bin只是一個例子。

-c 選項用來標明需要在執行腳本內容之前來執行的命令。

這條命令(-c選項後的命令)應該用引號括起來,以免在執行時被shell分開解釋。

-c選項可能會被反覆使用。多條命令可以使用同一個”-c”,命令之間需要用分號隔開。這些命令會按照它們出現的先後順序執行。(在Expectk中,”-c”相當於”-command”)。

-d選項允許輸出調試性信息。這些信息主要報告像expectinteract等命令執行時的內部行爲。這個選項與寫在腳本開頭的”exp_internal1”具有同樣的效果,同時還會打印出Expect的版本。(strace命令用在跟蹤變量聲明,trace命令用於跟蹤變量的賦值)(Expectk中,”-d”相當於”-diag”)

-D選項開啓交互調試器。後面必須跟有一個整數值作爲參數,當值爲非零或是按下CTRL+C的時候(或是遇到斷點,或是在腳本中恰好出現其他的調試語句),調試器會在進行下一次TclProcedure前取得控制權。想了解更多信息請參見README文件或是下面的SEEALSO。(在Expectk中,這個選項相當於”-Debug”)。

- f選項指明從哪個文件中讀取命令。這個選項是可選的,因爲只有當使用”#!”時它纔有可能被用到。而其他選項可以寫在命令行中。(在Expectk中,它相當於”-file”)。

默認情況下,命令文件是全部讀入內存一併執行的。但有些時候需要每次只讀一行。例如:stdin(標準輸入)就是這樣讀取的。如果強制任意文件以這種方式(每次讀一行)執行的話就使用”-b”選項。(在Expectk中,它相當於”-buffer”)。

如果”-“被一個文件名替代,那麼腳本就會用讀指定文件的方式來替代從標準輸入讀的方式。(例如:”./-“就表示從一個名爲”-”的文件中讀所需的信息)。

-i選項使Expect能交互式的提示輸入命令,而不是從文件中讀取。在遇到文件尾或是執行了exit命令時,提示輸入命令終止。要了解更多信息請參見下面的interpreter-i選項是假設既不是從一個命令文件讀,也沒有使用-c選項。(在Expectk中,它相當於”-interactive”)。

--是用來爲劃定選項尾的。當你需要像使用選項一樣傳一個參數,但希望這個參數不要被當作選項解釋時,就需要用到這個選項。當阻止其他選項時,可以把它放在”#!”行中。例如:下面的例子會讓所有參數(包括腳本文件名)都存儲在argv中。

#!/usr/local/bin/expect –

注意:當在”#!”行中使用參數時,必須遵守getopt(3)execve(2)的規定。

$exp_library下如果有expect.rc這個文件的話,它會自動被加載爲資源文件(應該是類似於標準配置文件,像用戶根目錄下的.bash_profile文件一樣)。除非使用-N選項取消自動加載。(在Expectk中,它相當於”-NORC”)。這個文件被加載後,緊接着用戶根目錄下的.expect.rc~/.expect.rc)會被加載。除使用-n選項取消。如果定義了環境變量DOTDIR,那麼它被認爲是存放有.expect.rc文件的目錄。然後從這個目錄中讀取.expect.rc文件。(在Expectk中,它相當於”-norc”)。這些加載配置文件的動作是出現在執行完-c選項指定的命令之後。

-v選項用來打印出版本號,然後退出。(在Expectk中的相應選項是-version)。

可選的參數匯成一列,存放在變量argv中。Argc被初始化爲argv的長度(變量個數)。Argv0被設置爲腳本名稱(orbinary if no script is used)。例如:下面的例子打印出腳本的名稱和前三個參數。

Send_user “$argv0 [lrang $argv 0 2]\n”

 

命令:

Expect使用Tcl語言(ToolCommandLanguage).Tcl提供諸如流控制,表達式值和一些其他的特性。像遞歸調用,定義函數等等。在這裏用到的沒有說明的命令都是Tcl命令。

Expect支持一些額外的命令。下面具體描述。除非另外聲明,否則命令返回空字符串。命令按字母順序排序,這樣便於查找。僅管如此,初學者還是覺得按照”spawn, send , expect , interact”這種方式來讀比較容易。

注意,ExploringExpect這本書中提供了關於”ExpectTcl”的介紹.這本Manpage手冊中也提供了一些例子.但數量有限,因爲這本是做爲入門的教材手冊使用的.

在本手冊中,E開頭的是指Expect程序,小寫e開頭的是指expect命令.

close[-slave] [-onexec0|1] [-ispawn_id]

關閉與當前進程的連接.大多數交互程序會在它們的stdin(標準輸入)中檢測到EOF(文件尾),然後退出.所以通常close也有能力殺死進程.-i選項指定了要殺死的對應於spawn_id的進程.expectinteract都能檢測到當前程序的退出,並隱含的執行一個關閉.如果你通過執行”execkill $pid”來殺死進程的話,那麼你就需要再顯式的調用一下close.

-onexec選項用來確定spawn_id在開始新的spawnedprocess(我將其翻譯爲監測進程)時是被關閉還是要被覆蓋.如果想保持這個spawn_id打開的話,那麼後面的參數需要設爲0.一個非零值將會使spawn_id關閉,並可以將這個spawn_id用於新的進程(默認行爲).-slave選項是用來關閉從屬進程.(參見spawn-pty)。如果在連接中止的時候,從屬進程還打開的話,那麼它將自動關閉。不管進程是顯式的調用或是隱式的被中止,你都需要調用wait命令來清理進程執行的殘餘。Close不會調用wait。因爲在關閉進程的時候,並不能保障它“正常退出”(個人認爲,可能是指退出時做相應的清理工作)。要了解更多的信息,請參見wait命令。

debug[[-now] 0|1]

控制Tcl調試器以步進方式執行語句,設置斷點等等。在沒有參數的情況下,如果調試器沒有運行,返回1,否則返回0。用1做參數時,啓動調試器,用0做參數時,停止調試器。如果連同-now一起使用的話,調試器將立即啓動(也就是說,在debug命令當中)。否則調試器會在執行下一條語句的時候啓動。

調試命令不會改變任何的traps。可以參見以-D選項啓動Expect(參見上面)

要了解更多關於debugger的內容,請參見README文件和下面的SEEALSO

disconnect

從終端關閉與一個克隆進程的連接,但讓它在後臺繼續運行。這個進程將被賦予爲單獨的進程組(如果可能的話)。標準I/O被重定向到/dev/null。下面的代碼使用disconnect命令使腳本在後臺繼續運行。

if[fork]!=0 exit

disconnect

下面的腳本需要讀取一個密碼,然後每小時執行一次,每次執行的時候都要求輸入密碼。腳本提供了所需的密碼,所以你只需輸入一次就可以了。(參見能關閉回顯的終端命令)。

send_user"password?\ "

expect_user -re "(.*)\n"

for {} 1 {} {

if [fork]!=0 {sleep 3600;continue}

disconnect

spawn priv_prog

expect Password:

send "$expect_out(1,string)\r"

. . .

exit

}

用這種方式,而不是用Shell後臺方式來執行程序的好處是,用disconnect可以在關閉前保存終端參數,然後把它們應用於新的終端中。如果使用&的話,Expect沒有機會讀取終端參數,因爲在Expect取得控制權的時候,終端已經退出了。

exit[-opts] [status]

使Expect退出或是準備退出。-onexit選項使下一個參數做爲退出的句柄被使用。沒有參數時,返回當前的退出句柄。-noexit選項使Expect準備退出,而不是把控制權暫時返還給系統。用戶定義的退出句柄和Expect內部的句柄都是以同樣的方式被返回。接下來將不再執行Expect命令。這在Tcl擴展環境下執行Expect時非常有用。保留當前的解釋器(Tk環境中的主窗口)以清除其他的Tcl擴展。如果Expect再次調用exit(這有可能發生),不會返回句柄。退出時,全部連接將關閉,關閉的動作會被監測的進程檢測爲“到文件尾”。exit只按照正常exit(2)的中的語句來執行,不會引發其他的動作。因此監視的進程如果沒有檢測到“到文件尾”的話,會繼續執行。(能檢測更多的情況是很重要的。例如:什麼樣的信號會發給監測進程,但這些是由系統決定的,它們放在exit(3)的文檔中)。如果被監測的進程繼續運行的話,將會被init繼承。當前的狀態信息將做爲Expect的退出信息返回(如果沒有指明的話,返回信息爲0)。退出命令在腳本程序執行的最後才自動隱含的調用。

Exp_continue [-continue_timer]

這個命令可以使expect繼續執行而不是正常的返回.默認情況下,exp_continue會重高超時時鐘,-continue_timer選項會阻止時鐘重新計數(連續計數).

Exp_internal [-f file] value

如果是value非零的話,使接下來的命令將調試信息輸出到Expect和標準錯誤輸出.如果是0的話,輸出的信息將會被屏蔽.調試信息包括收到的每條信息和每次嘗試用當前輸出與腳本中的模式相匹配的信息.如果設置了輸出文件,那麼正常的和調試的信息都會被寫到這個文件當中.(忽略上面value選項的值).任何之前打開的調試輸出文件將會被關閉.-info選項使exp_internal返回最近關於non-info參數的描述.

Exp_open [args] [-I spawn_id]

它返回對應於原始spawnid的文件描述符.這樣這個文件描述符就可以被使用了,就好像這個文件是被Tclopen指令打開的一樣.(這個spawnid將不再使用,wait指令將不能用在這個進程.).-leaveopen選項使spawnid保持打開,以便供Expect命令使用.

Exp_pid [-i spawn_id]

它將返回對應於當前被跟蹤進程的ID.如果使用-i選項,將返回對應於指定的spawnid的進程ID.

Exp_send

它是send的別稱

Exp_send_error

它是Send_error的別稱

Exp_send_log

它是Send_log的別稱

Exp_send_tty

它是Send_tty的別稱

Exp_send_user

它是Send_user的別稱

Exp_version [[-exit] version]它用於確保腳本程序與當前的Expect兼容。在沒有參數的情況下,返回當前Expect的版本.這個版本就會編譯到腳本中.如果你確切的知道你的腳本程序不需要最新版本的特性,可以指定一個以前的版本。

版本號由三個由句點分隔的數字組成.第一個是主序號.對應某一主序號版本的Expect寫的腳本程序,在不同主序號版本的Expect環境下基本不能正常運行.exp_version在主版本不同的情況下會返回一個錯誤.第二個數字是次版本號.編寫腳本的Expect的次版本號如果比當前的Expect大的話,可能會用到一些新的特性,在當前的環境下可能不能正常運行.exp_version會在當主序號相同,但次序號比當前Expect版本大的時候返回一個錯誤信息.第三個數字在Expect的版本比較中沒有多大作用.它只是當發行版有任何變化的時候會增加.比如說增加一些新的文檔或是做了優化.當升級到一個新的次版本號時,這個數字會被初始化爲零.如果使用了-exit選項,Expect會在當前的版本過期的時候打印一個錯誤信息,然後退出.

expect [[-opts] pat1 body1] ...[-opts] patn [bodyn]

等待直到被監視進程的輸出與設定的模式相匹配,或是一個指定的時間過後,或是遇到文件尾.如果最後的body是空的,那麼它將被忽略.最近的expect_before設定的模式會在其他模式之前被隱含地使用.最近的expect_after設定的模式將在所有其他模式匹配完後才被調用.如果整個Expect命令的參數超過一行,這個參數可能被分爲多行,各行之間用一個”\”連接,以防被分開解釋.在這種情況下,Tcl解釋器通常會置換掉”\”.如果一個模式設定爲eof.則相應的語句被在當達到文件尾的時候執行.如果一個模式設定爲timeout,那麼相應的語句會在超時時執行.如果沒有設定timeout對應的執行語句,將會在timeout時隱含執行空指令.即不執行任何語句.默認的超時時鐘設的是10,但可以自己設定.通過”settimeout30”,可以將超時時鐘設定爲30.如果設定爲-1的話,那麼超時時鐘將是無窮大,如果一個模式設定爲default,那麼相應的語句將會在遇到文件尾或是超時時執行.如果觸發了相應的模式,則此模式對應的語句將會被執行.Expect返回語句執行的結果(或是在沒有模式觸發的情況下是空字符串).在多種模式匹配的情況下,第一個匹配的模式對應的語句將被執行.

每次出現新的輸出的時候,它們都會依次匹配相應的模式.因此,如果你想測試匹配是否成功,可以把最後一個模式設定爲肯定會出現的東西,例如一個提示符.在沒有提示符的情況下,你需要使用一個timeout模式。模式被定義爲三種類型.默認情況下,模式被定義爲Tclstringmatch(字符串匹配)指令.(這些模式很像CShell中的正則表達式,它們通常被用來做模糊匹配).-gl選項保護那些可能被認爲是Expect的選項的模式匹配字符串。以”-”開頭的所有模式匹配字符串都需要這樣保護起來。(因爲默認情況下,以”-”開頭的字符串都被保留起來做爲將來的選項)。例如:下面的代碼期望一個正確的登錄.(注意下面的abort是一個已經在腳本的其他位置定義好的函數)

expect {

busy {puts busy\n ; exp_continue}

failed abort

"invalidpassword" abort

timeout abort

connected

}

在第四行需要使用引號,因爲它包含了一個空格.如果不用引號把模式括起來的話,它會被分別解釋爲模式與執行函數.執行同樣動作的其他模式同樣需要把執行函數的名稱寫出來(像其他兩個執行”abort”函數的模式),如果使用regexp-style模式的話(看下面的例子),更多關於建立glob-style模式的信息請參見Tcl手冊.regexp模式以-re開頭.上面的例子可以用regexp模式改寫成下面的代碼:

expect {

busy {putsbusy\n ; exp_continue}

-re"failed|invalid password" abort

timeout abort

connected

}

兩種類型都可以被正確匹配。這就是說設置的類型可以不用是整個字符串。可以只匹配頭部或是尾部(就假設其他部分也匹配一樣)。用^來匹配字符串頭部。用$來匹配字符串尾部。如果你不希望等待直到字符尾,你可以在監視進程回顯字符的中間時刻結束響應。雖然仍能打印出正確的結果,但最後的輸出可能看上去有點混亂。因此如果能夠詳細描述預期的字符串尾部的話,還是鼓勵使用$來匹配尾部。在許多編輯器中,^&分別表示首行和尾行。因爲Expect不是基於行緩衝的程序。所以這兩個字符分別用來表示當前匹配緩衝區中的頭數據和尾數據。-ex使模式進行精確匹配。這時,不對*,^等字符進行解釋(但還是要遵守Tcl的規則)。Expectpatterns are always unanchored.

-nocase選項使輸出中的大寫字符也按小寫字符匹配。模式匹配字符串本身改變。在讀取輸出進行匹配時,超過2000字符將會強制將前面的字符丟棄。這個數目可以通過match_max來改變。(但太大的數目會降低匹配的效率)。如果patlistfull_buffer,則在收到match_max個字節而沒有相應的模式匹配成功時,執行full_buffer所對應的語句。不管是否使用了關鍵字full_buffer,丟棄的字符都會被寫到expect_out緩衝區中。如果patlist是關鍵字null。並且空是有效字符(通過remove_nulls指令訪問),如果輸出是一個單個的ASCII0,那麼null相對應的語句將被執行。通過glob或是regexp模式是不能來匹配0字符。

在匹配字符串時(或是遇到文件尾,或是緩衝區滿full_buffer),任何匹配的或是前面沒有匹配的輸出都會被保存在expect_out緩衝區中。匹配到的9個字符分別被放到expect_out(1,string)expect_out(9,string)中。如果在模式前使用了-indices選項,那麼,這10個字符的開始字符和結尾字符在字符串中的位置被分別存放在變量expect_out(X,start)expect_out(X,end)中。其中X是自然數(應該是09)。0expect_out(0,*))是指整個匹配的字符串,它可以用於glob模式,也可以用於regexp模式。例如:如果一個進程的輸出爲“abcdefgh\n”,那麼expect“cd”的執行結果和下面的代碼執行結果是一樣的。

Set expect_out(0,string) cd

Set expect_out(buffer) abcd

“efgh\n”被丟棄到輸出緩衝區了。如果一個進程的輸出是”abbbcabkkkka\n”,那麼expect–indices –re “b(b*).*(k+)”的執行結果和下面語句的執行結果是相同的。

set expect_out(0,start) 1

set expect_out(0,end)10

setexpect_out(0,string) bbbcabkkkk

setexpect_out(1,start) 2

set expect_out(1,end)3

setexpect_out(1,string) bb

setexpect_out(2,start) 10

set expect_out(2,end)10

setexpect_out(2,string) k

set expect_out(buffer)abbbcabkkkk

“a\n”被丟棄了輸出緩衝區中了。含有”*”(-re“.*”)的模糊匹配的模式會清空輸出緩衝區,不再讀取從進程中輸出的字符。一般情況下,匹配的輸出會被Expect的內部緩衝區丟棄.可以通過在模式前加上-notransfer選項來避免被丟棄.這個選項在實驗時非常有用(爲了方便,可以簡寫成-not).與匹配輸出相對應的spawnid被存儲在expect_out(spawn_id).

-timeout選項使得Expect使用選項後面的數值做爲超時時間,而不是timeout變量中設置的時間.

默認情況下,設定的模式只與當前進程的輸出進行匹配.-i選項使得指定spawn_id或是spawn_id列的輸出與下面列出的所有模式進行匹配(直到下一個-i選項爲止).spawn_id列要麼是用空格分隔的一列spawn_id,要麼是用變量存儲的這要一列spawn_id.例如,下面的例子中,當前進程與”connected”進行匹配,由變量$proc2指定進程與”busy”,”failed”,”invalidpassowrd”進行匹配。

expect {

-i $proc2 busy{puts busy\n ; exp_continue}

-re"failed|invalid password" abort

timeout abort

connected

}

全局變量any_spawn_id的值是在當前expect中所有-i選項定義的spawn_id進程列的總和.它用來使這些spawn_id進程列與模式進行匹配。在一些-i選項中可能只給出了spawn_id,但沒有給出相應匹配模式.(例如,-i選項緊接下來就是另一個-i選項).那麼這些spawn_id列將會去匹配與any_spawn_id相對應的模式.

-i選項還可以定義一個全局變量,裏面存儲着spawn_id.當變量內容發生變化時,它會被重新讀取.這樣就可以在程序執行的時候改變I/O.以這種方式提供的spawn_id被稱爲”indirectspawn_id”.

Breakcontinue使流程(例如:for結構,proc函數)按照正常的順序執行.exp_continue使expect繼續執行而不是像通常一樣返回.這對於避免explicitloops(不執行以後有語句,直接進入下一次循環)和重複的語句很有用.下面的例子是一個自動rlogin的代碼片斷.exp_continue的使用避免了在rlogin揭示輸入密碼的時候的再寫一個重複的expect語句.(需要等待第二次提示)

expect {

Password: {

stty -echo

send_user"password (for $user) on $host: "

expect_user-re "(.*)\n"

send_user "\n"

send"$expect_out(1,string)\r"

stty echo

exp_continue

} incorrect {

send_user"invalid password or account\n"

exit

} timeout {

send_user"connection to $host timed out\n"

exit

} eof {

send_user \

"connection to host failed: $expect_out(buffer)"

exit

} -re $prompt

}

例如,下面的代碼使用戶可以在任務完全自動化的情況下,還能引導人機交互.這種情況下,終端被設置成原始狀態.如果按下”+”,那麼一個變量的值增加,如果按下”P”,那麼向進程發送幾個回車符,或是以其它的方式迴應一下.如果按下”i”,那麼用戶就會從腳本那兒把控制權收回,來與進程進行交互.在每個情況下,exp_continue都使在執行完當前的動作之後,繼續執行模式匹配.

stty raw -echo

expect_after {

-i $user_spawn_id

"p"{send "\r\r\r"; exp_continue}

"+"{incr foo; exp_continue}

"i"{interact; exp_continue}

"quit"exit

}

默認情況下,exp_continue會重置超時時間.如果以帶有-continue_timer選項的方式執行exp_continue的話,超時時鐘不會重新啓動.

Expect_after[expect_args]

它和expect_before的工作方式相同.expectexpect_after能同時匹配的情況下.進程與expect命令下面的模式進行匹配.想了解更多的信息請參見expect_before.

Expect_background [expect_args]

它和expect有一樣的參數列表.但不同的是它是立即返回.一旦有新的輸入到達時就開始進行模式匹配,timeoutdefault兩個模式對於expect_background來說沒有意義,它們會被隱含忽略.否則,expect_background會像expect一樣調用expect_beforeexpect_after的模式匹配.

expect_background在執行模式匹配時,對應於這個spawn_id的後臺進程將被阻塞.當執行完成時,後臺進程被解開.在後臺進程被阻塞期間,還可以在前臺以同樣的spawn_id執行一個expect腳本.但在非阻塞情況下是不可能這樣做的.在用同一個spawn_id聲明一個新的expect_background,前一個就會被自動刪除.聲明一個沒有匹配模式的expect_background將會使相應的spawn_id失去在後臺匹配模式的能力.

Expect_before [expect_args]

它和expect具有相同的參數列表.但不同的是它立即返回.相同spawn_id最近的expect_before下的匹配模式會自動隱含的加載到下面的expect命令中.如果其中一個模式匹配成功了,就好像匹配的模式是列在expect命令本身下面一樣.如果expect_beforeexpect的模式同時匹配,那麼將使用expect_before.如果沒有相應的匹配列出來,那麼這個spawn_id將不進行任何模式匹配的動作.

除非使用-i選項強制聲明,否則expect_before的模式將與執行expect_before命令時對應的spawn_id的進程輸出進行匹配(而不是有模式匹配成功時的spawn_id).-info選項會返回當前模式的詳細信息.默認情況,它會報告當前的spawn_id的信息。也可以通過指定spawn_id來顯示指定spawn_id的信息.

例如: expect_before–info –I $proc

這樣最多返回一個spawn_id的詳細信息.The flag -indirect suppresses direct spawn ids that come only fromindirect specifications.

-all選項使expect_before報告所有spawn_id的信息,而不是單個spawn_id的信息。

expect_tty [expect_args ]

expect的用法很像,但它是從/dev/tty讀取字符串(例如:用戶的擊鍵)。默認情況下,讀是工作在精加工緩衝模式下的。因此,每行之後必須以回車結尾,這樣expect才能分別識別它們。讀模式(例如行緩衝,等等)可以通過stty命令更改(參見下面的stty命令)

expect_user[expect_args]

expect的用法很像,但它是從stdin(標準輸入)讀取字符串(例如:用戶的擊鍵)。默認情況下,讀是工作在精加工模式下的。因此,每行之後必須以回車結尾,這樣expect才能分別識別它們。讀模式(例如行緩衝,等等)可以通過stty命令更改(參見下面的stty命令)。

Fork

創建一個新進程。這個新進程是當前進程的完整拷貝。成功時,會返回0給新進程,返回新進程的ID給當前進程。失敗時(失敗的原因可能是資源匱乏,如交換分區,內存不足等),返回一個-1給當前進程,沒有新進程創建。複製的新進程和它的父進程一樣通過exit命令退出。複製的新進程允許寫日誌文件。如果不屏蔽大多程序的debugging(調試)和logging(寫日誌)功能,結果(個人認爲:輸出結果或是日誌)看起來會顯得有點混亂。在多個用戶的情況下,即使是很短暫的pty執行結果,看起來也會很讓人混亂迷惑。因此,在監視某個進程(個人認爲是執行spawn)之前執行fork更好一點。

interact[string1 body1]... [stringn[bodyn]]

返回當前進程的控制權給用戶。所以擊鍵會被傳給當前進程(就像平時操作一樣)。當前進程的stdoutstderr也會返回(個人認爲:可能在腳本執行時,標準輸出和標準錯誤輸出是被重定向到Expect的,因爲執行spawn之後,expect會等待進程的輸出,包括錯誤輸出)。String-body被指定爲參數。在這種情況下,當有指定的string輸入時,對應的body就會被執行(默認情況,string不會被傳給當前進程)。如果沒有最後的body部分,那麼將執行interact命令。如果整個interact語句參數過長,超過一行,這些參數會用反斜線連接,分隔在多行,這樣避免了語句在執行時被隔斷。這種情況下,在Tcl進行語法解釋的時候會忽略這些反斜線,把這多行做爲一條語句來執行。例如,下面的代碼舉例說明了以string-body方式執行interact命令。String-body是這樣設定的:當你按下Ctrl+Z時,Expect將掛起,按下Ctrl+A時,用戶將會看到屏幕顯示“youtyped a control A”,並且也向當前進程發送一個Ctrl+A。當用戶按下$時,用戶會看到屏幕上顯示系統日期。按下Ctrl+CExpect將退出。如果輸入”foo”,用戶將在屏幕上看到“bar”,如果輸入~~,那麼Expect解釋器交互執行。

set CTRLZ \032

interact {

-reset $CTRLZ{exec kill -STOP [pid]}

\001 {send_user"you typed a control-A\n";

send"\001"

}

$ {send_user"The date is [exec date]."}

\003 exit

foo {send_user"bar"}

~~

}

string-body中,字符是按stringstring-body中出現的順序匹配的。

在不清楚餘下的字符是什麼的情況下,只是部分匹配的字符是不會被髮送到當前進程的。如果在獲得了餘下的字符之後,整個字符串沒有相應的string-body可以匹配(也就是說整個字符串在string-body中,沒有對應相同的string),除了上面說的匹配字符外,也沒有其他更多的匹配(個人理解:比如整個字符xxxbbccada.第一次提到的匹配字符xxxbb,string-body中有兩個對應的stringstring1=xxxbb,string2=xxxbbcc,那麼也就是說整個字符是沒有相應的string與之匹配,如果只有string1,沒有string2,那也就是“沒有更多的匹配”,只有xxxbb會發送到當前進程,如果存在string2,那麼我們最好把string2放在string1前面,這樣可以先在匹配string2,如果輸出字符串中,沒有相應的xxxbbcc,然後再去匹配string1。也就是說把“最大匹配”放在前面),那麼只有匹配的字符會發給當前進程。因此,我們可以把“部分匹配”放在後面,如果整個字符(或是“最大字符”)匹配失敗,我們再進行“部分匹配”。默認情況下,string匹配必須是精確完全匹配。(與之相反,expect命令默認使用glob-style模式)。-ex選項保證那些可能被解釋成interact選項的string能被正確執行。任何以”-”開頭的string都需要使用-ex。(所有以”-”開頭的字符將被做爲選項)

-re選項強制stringregexp模式解釋。這種情況下,像expect會把它的輸出存儲在變量expect_out裏面一樣,interact匹配的字符串也會在存儲在變量interact_out中。-indices選項的作用也和expect中的一樣。Eof模式列出了在遇到文件尾的時候要執行的語句。一個單獨的eof模式可能跟在-output選項後面,這樣當寫輸出遇到文件尾的時候,就會觸發eof模式,執行相應的語句。默認的eof行爲是返回,所以執行interact命令時,在遇到文件尾就是返回。Timeout模式介紹了超時(以秒爲單位)的概念,並列出了(超時)連續數秒沒有讀取到字符後的執行語句。Timeout作用於最近指定的進程。**這裏沒有默認的timeout,特殊變量timeoutexpect命令裏面使用的)對這裏的timeout模式沒有影響。**例如,下面的命令可以用於自動退出用戶,他們在一小時之內沒有輸入任何字符,卻一直收到系統消息。

interact -input $user_spawn_id timeout 3600 return –output $spawn_id

如果模式爲關鍵詞null,而且null是允許的(通過remove_null命令),則在輸出中如果出現單個的ASCII0,那麼null對應的語句將被執行。在globregexp模式下是不可能完成的。在模式前加上-iwrite選項,將會把匹配成功(或是遇到文件尾)的進程的spawn_id賦值給變量interact_out(spawn_id)Breakcontinue會使控制結構(for循環,子函數等等)按照正常的方式運行,但return會使interact把信息返回給它的調用函數。Inter_return會使它的調用函數返回。例如,如果一個子函數foo調用了inter_return,在執行inter_return時,子函數foo會返回。(這就是說,當interact交互式調用解釋器時,如果輸入return,那麼交互還將繼續,如果輸入inter_return,那麼interact將返回)

interact執行過程中,終端工作在“原始狀態”下,這樣所有字符都將發送給當前進程。如果當前進程沒有捕獲到工作流程的信號,那麼按下Ctrl+Z會使其中止。如果想重啓這個進程,可以給它發送一個“繼續”信號(如執行:"kill -CONT <pid>"),如果你真想給當前進程發送一個“中止”信號,你可以考慮先監視csh,然後再啓動你的程序。也就是說,如果你想發送中止信號給Expect,首先要調出解釋器(可能是按一下ESC),然後按下Ctrl+Z

爲了避免進入解釋器,交互式的執行命令,string-body可以用做“速記”,當string-body對應的body執行的時候,使用的前一個終端模式。爲了程序的執行效率,默認情況下,終端使用原始狀態。-reset使終端恢復到interact執行以前的狀態(總是“精加工”狀態)。注意的是,在進行終端模式轉換的時候,此時輸入的字符可能丟失(在一些系統上,會出現這種

糟糕的現象)。最好在你必須使用”精加工”模式再使用-reset選項。-echo選項使與模式進行匹配的字符同時也被髮送給產生這些字符串的當前進程,就好像是當前進程讀取到他們一樣。這在當用戶希望在執行某些指令需要看到回顯的時候非常有用。

如果回顯了一個模式,但最終沒有匹配成功,這些字符會被髮送到監視的進程,如果監視的進程再把它們顯示出來的話,那麼用戶將會看到他們兩次。-echo可能僅僅適合於當用戶不可能不完成模式匹配的情況。例如:下面是摘自於rftp,一個遞歸式ftp腳本,用戶被提示輸入”~g,~p,~l”,以便遞歸的”獲得,上傳,查看”當前路徑。這些字符和常規的ftp命令相差太遠,用戶除非出錯,否則基本上不會打出”~”後面跟有某些字符的情況。這種情況下,他們就可能會忽略了正確的結果。

interact {

-echo ~g{getcurdirectory 1}

-echo ~l{getcurdirectory 0}

-echo ~p{putcurdirectory}

}

-nobuffer選項會把進行模式匹配的字符發送給輸出進程,就像這些字符是被讀取的一樣。這在你想讓進程回顯模式的時候非常有用。例如,下面的代碼監視了哪個用戶在撥叫(一種Hayes模式的Modem),每次都會在腳本的日誌文件中後面看到一個”atd”

proc lognumber {} {

interact -nobuffer-re "(.*)\r" return

puts $log "[execdate]: dialed $interact_out(1,string)"

}

interact -nobuffer"atd" lognumber

在交互過程中,log_user的前一個值被忽略了。特別需要說明的是,interact會強制使他的輸出記錄成日誌(輸出到標準輸出),因爲它認爲用戶不希望沒有任何迴應的交互。-o選項使下面的key-body模式應用於當前進程的輸出(也就是說用當前進程的輸出來匹配模式)。這對於處理像”在一個telnet會話中輸入很多錯誤字符(個人認爲:非命令或是選項字符)”的情況非常有用。

默認情況下,interact希望用戶對標準輸入進行寫操作,對標準輸出進行讀操作。-u選項通過指定進程名(通常是指定一個spawn_id)來使此進程的用戶與其他進程進行interact(交互)。這就使兩個毫不相差的進程通過這樣一個聯繫連接起來。爲了協助調試,Expect的調試信息經常會輸出到標準錯誤輸出(或是是標準輸出,爲了記錄日誌和調試信息)。同樣,解釋器也會交互的從標準輸入讀取字符。例如:下面的代碼,建立了一個登錄進程,它呼叫用戶,然後使雙方連接在一起。當然其他進程也可以取代這裏的login進程。一個腳本,允許在不提供用戶名與密碼的情況下正常工作。

spawn login

set login $spawn_id

spawn tip modem

# dial back out touser

# connect user tologin

interact -u $login

爲了發送輸出給多個進程,必須使用-output選項指定spawn_id列表。同樣,要給多個進程輸入字符,需要使用-input選項(-input-output,還有expect中的-i選項都支持列表,除了特殊變量any_spawn_idinteract命令中無效,在expect中有效)

所有接下來的選項或字符串(或模式)對當前的輸入有效。直到下一個input選項爲止。如果沒有-input選項,-output選項暗含表示”–input$user_spawn_id–output”(在不含有-input選項的模式中也一樣)。如果指定了一個-input選項,那麼它將覆蓋$user_spawn_id,如果出現第二個–input選項,那麼它將覆蓋$spawn_id,還有可能會指定更多的-input選項。

這兩個暗含的輸入進程把它們的輸出默認分別把$spawn_id$user_spawn_id作爲它們的輸出(做了調換)。如果-input選項後面沒有-output,那麼這個進程的輸入將會被忽略。-i選項介紹了一種當沒有使用-input或是-output選項時的替代方式。-i選項暗含一個-o選項。

使用“間接”spawn_id列可以改變交互進程(“間接”spawn_id列已經在expect命令裏面講過)。“間接”spawn_id列可以通過-i-u-input或是-output選項指定。

interpreter [args]

使用戶交互的輸入Expect或是Tcl命令,每個命令的結果都會被打印出來。

Breakcontinue會使控制結構(for循環,子函數等等)按照正常的方式運行,但return會使interact把信息返回給它的調用函數。Inter_return會使它的調用函數返回。例如,如果一個子函數foo調用了inter_return,在執行inter_return時,子函數foo會返回。其他命令使interpreter繼續提示輸入新的命令。默認情況下,提示包含兩個整數。第一個表示thedepth of evaluation stack嵌套的層數(也就是Tcl_Eval被調用了多少次)。第二個參數是Tclhistoryidentifier歷史指針。提示符可以通過定義一個叫做”prompt1”的子函數來設置,這個子函數的輸出會成爲下一個提示符。如果一條語句中包含半開的(也就是一個,不是一對兒)引號,大括號,中括號或是小括號,那麼下一個提示符會被放在新一行。第二個提示符同樣也可以通過定義一個叫做”prompt2”的子函數來設置。在interpreter執行過程中,終端使用“精加工”模式,即使它的調用函數使用的是“原始中”模式。如果在沒有使用-eof選項的情況下,標準輸入被關閉,那麼interpreter就會返回。如果使用了-eof選項,那麼將調用下一個參數。

log_file [args] [[-a] file]

如果指定了文件名,那麼log_file命令會把會話的記錄寫入文件(從執行這條語句開始),如果沒有給定任何參數,那麼log_file命令會停止記錄。前面的日誌文件都將被關閉。不指定文件名,還可以通過-open或是-leaveopen選項來指定Tcl文件描述符,這和spawn命令的用法一樣(參見spawn命令)-a選項強制把log_user命令產生的輸出記錄到日誌。默認情況下,爲了在一次會話中能很方便的多次關閉日誌記錄,log_file命令會把輸出信息添加到文件尾,而不是覆蓋原來的內容。如果想覆蓋原來的內容,可以使用-noappend選項。-info選項使log_file命令返回關於最近的non-info(info選項)參數的描述。

log_user -info|0|1

默認時,send/expect對話會被記錄到標準輸出中,可以通過log_user0來禁止,通過log_user1來恢復。輸出到日誌文件維持不變。-info選項使log_user命令返回關於最近的non-info(info選項)參數的描述。

match_max [-d] [-i spawn_id] [size]

這個命令定義Expect內部使用的緩衝區大小。如果沒有參數,返回當前大小。如果使用-d選項的話,將緩衝區設置爲默認大小(初始的默認大小是2000Bytes)。如果使用了-i選項,那麼設置的是對應於spawn_id的進程的緩衝區大小。否則設置的是當前進程的。

overlay [-# spawn_id] [-# spawn_id][...] program [args]

終止當前的Expect程序,執行programargs。一個連字符沒有指定參數,那麼連字符將被放到命令之前,就像它是一個登錄Shell一樣。除了那些在命令行中做爲參數指定的spawn_id外,其他將全部被關閉。這些在命令行中指定的spawn_id將被重定向到指定的文件描述符。這些spawn_id被重定向到文件描述符是爲了新程序來繼承。例如,下面的命令運行chess程序,而且允許當前程序--chessmaster(chess控制者)來控制。

overlay -0 $spawn_id -1 $spawn_id -2$spawn_id chess

雖然它犧牲了執行程序化交互的能力,因爲Expect已經失去控制權,但還是要比”interact–u”更有效率。注:在這裏,沒有提供控制終端,因此,如果你斷開或是重定向了標準輸入,那麼控製作業的程序(Shell,login等等)將不能正常訪問。

parity [-d] [-i spawn_id] [value]

定義parity是否需要與當前監視的進程的輸出分隔開。如果設爲0,則是分隔開,否則將不分開。如果沒有參數的話,將返回當前值。-d選項將parity設置爲默認的值(初始默認值爲1,不分開)-i選項用來指定需要設置parity值的,對應於spawn_id的進程。否則設置當前進程的parity值。

remove_nulls [-d] [-i spawn_id][value]

此命令用來定義null在匹配模式或是存儲到變量expect_out或是interact_out之前,是否需要與監視進程的輸出分隔開。如果設爲1,則分開,如果爲0,則不分開。沒有參數,返回當前值。-d選項用來把它設爲默認值(初始默認值爲1,分隔開).-i選項用來設置對應於spawn_id的進程的remove_nulls的值。如果沒有使用-i選項,那麼將設置當前進程的remove_nulls的值。不管null是否被分隔,Expect都會把它們記錄到日誌和標準輸出中。

send [-flags] string

發送字符串給當前進程。例如”send “hello world””會向當前進程發送“hello空格world回車”(Tcl提供了一個類似於printf的命令,它可以構建任意複雜的字符串)。儘管工作在行緩衝模式下的程序直到遇到回車的時候纔會讀到這些字符,但這些字符還是在輸入的時候就被立即發送出去了。回車被表示爲”\r”

--選項使下面的參數做爲字符串而不是選項。所有的字符串,不管它是否像選項,都可以前綴一個”--”。這提供了一種安全機制:不會再去費力區分很像選項的字符串到底是什麼了。(所有以”-”開頭的字符串將作爲選項使用)

-i選項指定字符串將發往對應於spawn_id的進程。如果指定的spwan_iduser_spawn_id的話,而且終端工作在原始狀態下,那麼字符串中的換行將被轉換爲回車換行,以使它們看起來像是工作在“精加工“狀態下。-raw選項禁止這種轉換。-null選項用來發送null字符。默認情況是發送一個null字符。可以通過設定一個整數來確定發送多少個null字符。-break產生一箇中斷條件。這隻有對使用spwan_open命令打開tty設備對應的spawn_id時纔有效。如果你監視了類似於tip的程序,那麼你應當遵循tip的規則來產生一箇中斷。-s選項強制發送速度變慢。這是避免計算機輸入時,會出現輸入緩衝區溢出。因爲某些輸入緩衝區是以人的輸入速度爲標準設計的。輸出的速度由變量send_slow的值來限定的。它帶有兩個參數。第一個是一個整數,它用來描述自動發送的字節數。第二個參數用來設定發送的時間間隔。例如:setsend_slow{10 .001} 將使”send-s”在每發10個字符後停1毫秒,然後再發10個字符。-h選項使發送模擬人的擊鍵速度。每個字符之間的時間間隔也像人手敲擊間隔。(這種算法是基於WeiBull規則,它帶有修正值以適應特殊情況)這種輸出的速度通過變量send_human中的5個參數來設定。前兩個參數是字符敲擊之間的平均時間,第一個是默認使用時間。第二個是輸入一個詞後的停頓時間。以模擬這種轉變過程中(從連續輸入字符到輸入一個詞再輸入另一個詞的轉變)的微小停頓。第三個參數是穩定度,.1表示非常不穩定,1表示相對不穩定,10表示非常穩定。極限是0和無限大。最近兩個參數分別指定了最小和最大的間隔時間。它們用來修正最終時間。如果最小和最大有大量的取值範圍,最後的平均時間可能會和給定的平均間隔時間有很大的差別。

例如:下面模擬了一個快速穩定的打字員

set send_human {.1 .3 1 .05 2}

send -h "I'mhungry. Let's do lunch."

下面模擬的更適合於一個經常有較大停頓的打字員

set send_human {.4.4 .2 .5 100}

send -h "Gooddparty lash night!"

注:錯誤是不可以模擬的。雖然你可以通過在send命令參數裏面嵌入錯誤和更正來設定錯誤更正模式。

這些發送null字符,設定中斷條件和強制發送變慢,模擬人的輸入的選項之間都是互斥的,只有最後的選項有效,還有需要說明的是,發送null字符和設定中斷條件選項是不能帶有其他參數的。

在第一個send前加一個expect命令是個不錯的主意,因爲expect會等待進程開始,而send不會等待。在某些情況下,進程還沒有開始,你的第一個send已經發送完畢了,這樣你就會有把你的數據丟失的危險。在某些交互程序沒有初始化提示符的情況下,可以在send之前加一個延時。

# To avoid giving hackers hints on how to break in,

# this system does not prompt for an externalpassword.

# Wait for 5 seconds for exec to complete

spawn telnet very.secure.gov

sleep 5

send password\r

exp_sendsend的別名。如果你使用的是Expectk或是Tk環境下某些Expect的變體或是擴展,那麼Tk會把send做爲與現在完全不同用途的命令。

send_error [-flags] string

用法很像send,除了它是把輸出發送到stderr(標準錯誤輸出),而不是當前進程。

send_log [--] string

用法很像send,除了它是把string發送給logfile。如果沒有logfile打開的話,那麼參數將被忽略。

send_tty [-flags]string

用法很像send,除了輸出是發往/dev/tty而不是當前進程。

send_user [-flags] string

用法很像send,除了輸出是發往標準輸出stdout而不是當前進程。

sleep seconds

使腳本停頓給定的秒數。Seconds是十進制數。在Expect停頓期間,中斷(或是在使用Expectk時的Tk事件)也會正常響應。

spawn [args] program [args]

創建一個執行programargs命令的進程。它的stdin,stdou,stderr(標準輸入,標準輸出,標準錯誤輸出)都連到Expect。這樣它們可以被其他的命令讀取和寫入。執行close命令或是進程本身關閉這三個描述符之中的一個都將關閉這個連接。當用spawn開啓一個新進程時,這個進程的描述符將賦給變量spawn_idSpawn_id指定的進程被認爲是當前進程。爲了有效的提供作業控制,可能需要讀寫spawn_idUser_spawn_id是一個全局變量,它指定了用戶的ID,例如:把user_spawn_id賦給spawn_id時,expect將以expect_user方式執行。Error_spawn_id也是一個全局變量。它包含了指向錯誤輸出的描述符。例如:當spawn_id賦了這個值的話,那麼send將以send_error方式工作。Tty_spwan_id也是一個全局變量,它包含了一個指向/dev/tty的描述符。如果/dev/tty不存在(例如在一個cron任務,at任務,或是batch腳本中,無人操作),那麼tty_spawn_id將不被賦值。可以通過下面的代碼得到驗證。

if {[info vars tty_spawn_id]} {

# /dev/tty exists

} else {

# /dev/tty doesn'texist

# probably incron, batch, or at script

}

spawn命令會返回UNIX進程的ID。如果沒有監視進程,那麼返回0。變量spawn_out(slave,name)將保存pty從設備的名稱。默認情況下,spawn命令會回顯命令和參數,-noecho禁止回顯。-console選項使控制檯的輸出重定向到監視的進程。但並不是所有系統都支持這一選項。Spawn內部使用pty。它像用戶的tty一樣初始化。它還將更進一步的初始化,以”完善”功能(對應stty(1))。如果設置了變量stty_init,它將以stty參數的形式執行進一步初始化。例如:“setstty_initraw”將會使以後監視的進程終端以原始方式啓動。–nottycopy選項將忽略(不執行)基於用戶tty的初始化。–nottyinit選項將忽略(不執行)“完善“的初始化。正常情況下,spawn的執行速度很快,如果你發現spawn執行的非常慢,它很有可能遇到了錯誤的pty,爲了避免錯誤進程的干擾,在pty方面做了大量的測試(每個錯誤的pty會花費10秒鐘)。使用-d選項執行spawn命令會看到Expect是否工作在不穩定狀態的pty上面。如果不能殺死與這些pty對應的進程,那麼唯一的辦法就只有重啓。如果因爲exec執行失敗(沒有program這個程序),造成program不能正常被監視,那麼在執行下一個interact或是expect命令時會返回一個錯誤信息。就像程序已經執行,然後產生了錯誤信息一樣。這是spawn正常的運行結果。在spawn執行的時候,如果執行fork複製進程,之後監視的進程和原來的Expect程序之間只能通過spawn_id互相通信。-open使下一個參數被解釋爲Tcl文件描述符(也就是說由open命令返回的)。可以把它作爲spawn_id使用,就像它是一個被監視的進程一樣(文件描述符將不再使用)。這讓你在不使用pty的情況下,監視原始設備,文件和管道。如果返回0,表示沒有相關聯的進程。當與監視進程的連接關閉之後,Tcl文件描述符也將關閉。-leaveopen選項用法很像-open選項,除了監視進程被關閉,Tcl文件描述符仍然保持打開之外。-pty選項打開一個pty,但不監視進程。返回0表示沒有相關聯的進程。Spawn_id還像以往一樣賦值。對應於ptyslave的文件描述符賦給變量spawn_out(slave,name)。可以通過close-slave關閉ptyslave-ingnore選項定義了在監視進程過程中可以忽略的信號。如果沒有定義的話,那麼對應相應的信號將執行相應的動作。這裏的信號命名方式與trap命令中的一樣。只是這裏每個信號要分別使用一個單獨的標記。

strace level

使接下來的語句在執行之前首先被打印出來(Tcltrace命令跟蹤變量)Level定義了跟蹤幾層。例如,下面的語句執行Expect程序,只跟蹤4層調用,其他不管。

expect -c "strace 4"script.exp

-info選項使strace命令返回最近的非info選項參數的描述。

stty args

像內部命令stty一樣,改變終端的工作模式。默認情況下,控制終端是可以被訪問的。其他終端通過在文件尾添加”</dev/tty..”,也可以被訪問(注意:這些參數不要組合成一個單一的參數)。終端狀態將作爲命令結果返回。如果沒有請示終端狀態,而且控制終端可以訪問,那麼前一個“原始”狀態和顯示屬性將以一種命令可識別的方式返回。例如:參數raw或是–cooked使終端工作在原始狀態,而-raw或是cooked使終端工作在“精加工“狀態。參數-echo和–noecho分別設置終端爲”回顯“和”不回顯“方式。

下面的例子演示了怎樣間斷性的關閉回顯。這可以用在那些避免將密碼嵌入到腳本中的自動執行程序(參見下面的ExpectHints關於這方面的討論)

stty -echo

send_user "Password:"

expect_user -re"(.*)\n"

set password$expect_out(1,string)

stty echo

system args

args傳給shell作爲輸入,就好像是從終端輸入的一樣。Expect會等待直到shell執行結束。從shell的返回狀態的處理方式和exec處理它自己的返回狀態一樣。與exec不同的是:exec會把標準輸入和標準輸出重定向到腳本,而system不做重定向(除非在要執行的語句內部指定)。因此,它可以用於必須直接與/dev/tty對話的程序。同理,system的執行結果也不記錄到日誌裏面。

timestamp [args]

返回一個時間戳。沒有參數的話,返回從epoch開始到現在的秒數。-format選項指定了一個要返回的時間字符串,返回的時候這個字符串中的相應字符將按照POSIX規則替換。例如:a%會被工作日的簡寫形式所代替(例如:Sat)。其他的如下:

%a abbreviated weekday name

%A full weekdayname

%b abbreviatedmonth name

%B full monthname

%c date-time asin: Wed Oct 6 11:45:56 1993

%d day of themonth (01-31)

%H hour (00-23)

%I hour (01-12)

%j day (001-366)

%m month (01-12)

%M minute (00-59)

%p am or pm

%S second (00-61)

%u day (1-7,Monday is first day of week)

%U week (00-53,first Sunday is first day of week one)

%V week (01-53,ISO 8601 style)

%w day (0-6)

%W week (00-53,first Monday is first day of week one)

%x date-time asin: Wed Oct 6 1993

%X time as in:23:59:59

%y year (00-99)

%Y year as in:1993

%Z timezone (ornothing if not determinable)

%% a bare percentsign

其他%形式沒有指定的,那麼相應的字符在返回時不改變。只支持C規範。

-seconds選項指定了一個整數。這個整數用來確定epoch。它是從epoch到現在的秒數。用epoch時刻的時間做爲樣本,來格式化時間。如果沒有參數,那麼將使用當前時間作爲樣本。

-gmt選項強制使時間戳的輸出使用GMT時區模式。如果沒有參數,使用當前時區。

trap [[command] signals]

在將來收到任何指定信號時,執行指定的命令。這個命令在全局範圍內有效。如果指定的命令不存在,那麼信號對應的執行動作就是返回。如果命令是SIG_IGN,那麼信號將被忽略。如果命令是SIG_DFL,那麼信號將執行系統的默認動作。Signals可以是一個信號,也可以是一組信號。信號可以以數字表示,也可以像signal(3)中定義的那樣以符號表示。可以省略前綴”SIG”。沒有參數的話,返回trap命令執行時的信號碼。-code選項在命令最初開始執行的時候,返回命令的返回碼,而不是返回Tcl的返回碼。

-interp選項使在命令執行的時候,而不是命令聲明的時候,用interpreteractive給命令賦值。

-name選項返回trap命令當前被執行時的信號名。

-max選項返回最大的可以設置的信號碼

例如:”trap{send_user “Ouch”}SIGINT會在每次用戶按下Ctrl+C時打印出”Ouch”。默認情況下,SIGINT(按下Ctrl+C實現)SIGTERM會使Expect退出,這是因爲Expect起動時會默認執行trapexit{SIGINT,SIGTERM}。如果你用-D選項啓動了調試器,那麼SIGINT被重新定義爲啓動交互調試器。這是因爲默認執行了trap{exp_debug 1}SIGINT。這個跟蹤命令可以通過修改變量EXPECT_DEBUG_INIT中的值來改變。當然你也可以通過在腳本中增加相關的trap命令來覆蓋它們。特別說明的是,如果你寫了”trapexit SIGINT”,那麼這將覆蓋掉”trap{exp_debug 1}SIGINT(系統默認)。這在你阻止用戶進入調試器非常有用。如果你想在腳本運行的時候還能跟蹤調試器,而且還能定義自己的“SIGINT“信號對應執行指令。那麼你就用”if![exp_debug] {trap my_CMDSIGINT}”。當必須出選擇時,你可以通過其它信號來調用調試器。Trap命令不允許覆蓋關於信號SIGALRM的對應執行動作。因爲它是Expect內部使用的信號。Disconnect命令會把SIGALRM設置爲SIG_IGN(忽略)。瞭解更多信息請參見signal(3)

wait [args]

等待直到一個監視的進程中止。(如果沒有指定進程,則是當前進程)Wait命令會返回一個4個整數列。第一個是監視進程的ID,第二個是對應的spawn_id,第三個如果爲0表示成功,-1的話表示操作系統有有錯誤產生。如果第三個參數爲0,第4個參數爲監視進程的返回狀態信息。如果第三個參數爲-1,第4個參數爲系統設置的錯誤碼。全局變量errorCode也將被設置。在wait命令得到的返回信息中,還可能會出現其他元素。第五參數(可選的)表明了信息的級別。目前唯一可用的值是CHILDKILLED,這種情況下,接下來的兩個值分別是C-Style的信號名和一個短的文字性描述。-i選項指定要wait的進程是由spawn_id指定的進程(不是進程ID)。在SIGCHLD的句柄裏,通過指定spawn_id-1,可以wait任何監視的進程。

-nowait選項使一旦成功執行wait之後,立即返回。這樣當進程退出(以後),不需要直接再顯式執行wait。進程就可以自己消失。通過使用”-i-1”wait還可以用於等候一個複製的進程。不像它在其它監視進程裏面的用法,這裏它可以在任何時候執行。這時對於“哪個進程返回了”沒有控制,但可以通過檢測每個進程ID對應的返回值。

 

庫文件

Expect自動關聯兩個爲Expect內建的庫文件。它們定義在變量exp_libraryexp_exec_library定義的目錄裏。兩個目錄都是用來存放爲其他腳本文件使用的有用文件。Exp_library包含有結構獨立的文件。Exp_exec_library包含了體系依賴的文件。依賴於系統,兩個文件夾可能都是空的。存在”$exp_exec_librara/cat-buffer”與否表明你的/bin/cat是否工作在默認的緩衝模式下。

 

漂亮的格式化顯示

規範化Expect腳本文件的格式是可行的。假設支持Expectvgrind功能插件已經安裝。你可以這樣執行:vgrind–lexpect 腳本名

 

例子

也許手冊沒有很直接的告訴你怎麼把手冊上的例子放到一起應用。我還是鼓勵你把Expect目錄下的例子全部嘗試一遍。其中的一些是真實的程序。另外的一些也展示了一定的技巧。當然其中一些只是hacksINSTALL文件中有一個程序的簡介。Expect也是很有用的。其中的一些規則使用的是較早版本的Expect。其中的原理仍然有效,只不過現在可能又形成了比上面介紹的更多的細節。

 

警告

擴展屬性可能會與Expect命令有衝突。例如,在Tk中,send被定義爲與現在完全不同的用途。同理,很多其他命令在Tk中是以exp_xxx方式來使用的。以expinterspawntimeout開頭的命令和變量是沒有別名的。如果你想保證兼容性,必須使用擴展的命令名稱。Expect提供了相對自由的作用範圍。特別說明的是,從命令行爲Expect程序讀取的變量的賦值首先被當前作用域截取,如果沒有相應的變量,則在全局中查找。例如,這避免了在每個執行expect的進程裏面放上“globaltimeout”。另一方面,變量一般是以局部方式定義的(除非使用一個“全局命令“)。引起的最普遍的問題就是當在一個進程內部執行spawn命令的時候,在這個進程外部,spawn_id不再存在,所以由於作用範圍的問題,使監視的進程不能再訪問。在這樣的進程裏,可以加上一個全局變量spawn_id。如果你不能啓用多進程監視(你的系統不支持選擇,也不支持poll,或是類似的功能)Expect只能一次控制一個單個的進程。這種情況下,不要試圖設置spawn_id,也不要在一個監視進程運行的時候通過exec執行新的程序。這樣就是說,你不能用Expect來同時監視多個進程。

終端參數對腳本也有很大的影響。例如,如果腳本想要回顯,但如果終端關閉了回顯,那麼腳本執行將不能實現預期的結果。就是因爲這樣,Expect強制“完善“的終端模式。不幸的是,這可能對其他某些程序來說不太方便。例如,Emacs想改變常見的繪製規則:把多行映射到多行,而不是回車換行分隔的多行,並且關閉回顯,這樣可以使用戶用Emacs編輯輸入。但不幸的是,Expect不能做到這些。

你可以使Expect不覆蓋終端的默認參數,不過你要爲這種環境寫腳本要非常小心。在Emacs的例子中,要避免做依賴於回顯或是換行的工作。帶有很多參數的命令被連成一列(expect變量和interact),它需要一個標記來判斷它是一個參數還是多個。當本來是一個參數的命令中有很多的”\n”,而且沒有空格的時候,Expect將不能正確判斷。這聽起來好像完全不可能,然而-nobrace可以把一個參數強制按一個參數來解釋。這可以應用在由機器產生的Expect碼中。

 

漏洞

本來是要把這個程序命名爲”sex”(for either "Smart EXec" or"Send-EXpect")。但最後還是以更文雅的方式命名了。在一些系統上,當監視一個Shell時,它會在不能訪問tty時仍然運行。這就是說,你的系統有控制tty的機制,但Expect不瞭解。如果你知道爲什麼,請告訴我。

Ultrix 4.1(至少是目前最新的版本)會把timeout的值超過1000000時認爲是0

Digital UNIX4.0A(可能是其他版本)如果定義了SIGHLD,會拒絕分配ptys

IRIX6.0不能正確處理的pty權限,所以如果嘗試用前面某個人的pty,它將會失敗。請更新到IRIX6.1

如果沒有設置TERMTelnet會掛掉(只在SunOS4.1.2下面證實)。這也是cronat,和cgi腳本下的問題,因爲它們沒有設置TERM。因此必須對TERM賦值,賦給它一個不會影響正常運行的類型。下面的設置應該適合大部分情況。

set env(TERM) vt100

如果沒有設置SHELLHOME的話,Tip會掛起(只在BSDIBSD/OS 3.1i386下證實過)。這也是cronat,和cgi腳本下的問題,因爲它們沒有設置這些環境變量。因此必須對這些環境變量賦值,賦給它們一個不會影響正常運行的類型。這些變量是必須要賦值的。下面的語句應該適合大部分情況。

set env(SHELL) /bin/sh

set env(HOME) /usr/local/bin

一些pty的執行被設置成:在進程把文件描述符關閉之後,如果10或是15秒沒有讀取輸出,那麼這些輸出將被丟棄(具體的時間視執行情況而定)。因此像下面的Expect程序會失敗。

spawn date

sleep 20

expect

爲了避免這樣,可能通過exec執行非交互式程序,而不是使用spawn。這雖然是可信的,但在實踐當中,我從來沒有遇到在一個交互式程序時,由於上面的BUG而出現丟失字符的現象。另一方面:CrayUNICOSpty會在進程關閉文件描述符的時候立即丟棄所有未讀取的輸出字符。我已經通知了Cray,它們正在修補這個BUG。有時候,在提示和響應之間需要做一個停頓。例如,當tty正在改變UART設置或是通過檢測起始位來匹配波特率的時候。通常,這些都需要停頓12秒。一種更智能的技術就是不斷嘗試,直到硬件準備接收輸入爲止。下面的代碼應用了這兩種技術。

send "speed 9600\r";

sleep 1

expect {

timeout {send "\r";exp_continue}

$prompt

}

trap–code不能正確處理位於Tcl事件環內部的命令,比如說sleep。問題的原因是:在Tcl事件環內部,Tcl會忽略異步事件的返回碼。在trap碼內部設置一個工作區,裏面設置一個標記。然後在命令(也就是sleep)執行之後立即檢查標記。

Expect提示

這有一些關於Expect的非直觀的東西。這一節通過一些建議講述這些事情。常見的Expect問題是它怎樣標識認出Shell的提示符。因爲它們會因用戶和Shell的不同而不同。自動登錄在不知道提示符的情況下會變得很困難。一個可取的慣例是讓用戶把一個能夠描述這此描述符的常規表達式賦值給一個環境變量EXPECT_PROMPT。代碼可以寫成如下所示,如果EXPECT_PROMPT不存在,代碼也能正常運行。

set prompt "(%|#|\\$) $" ;# default prompt

catch {set prompt$env(EXPECT_PROMPT)}

expect -re $prompt

我建議你把輸出字符的結尾包含到你的pattern中,這樣可以不用看到整個字符,就可以做出響應。另外,當你可以在不看到整個字符串做出響應的時候,如果你回答的過早,你的答案可能會被顯示在問題的中間。換句話說,這樣的話,雖然你的結果是對的,但會話記錄看起來有點混亂。大多的提示符最後包含一個空格。例如ftp的提示符是“f”,”t”,”p”,”>”<blank>。要匹配這個字符串,你要考慮每個字符。丟掉空格是常見的錯誤,一定要加上空格。

你如果使用”X*”這種模式,那麼*會匹配所有從X後開始到最後收到的東西。這聽起來很直觀,但是“最後收到的東西“讓人感覺有點迷惑。這和機器的速度,Kernel和設備驅動處理I/O的方式都有關係。特別的是,人們趨向於認爲輸出是成塊的傳輸。但實際上大部分程序每次只產生一行輸出。如果是這樣的話,那麼上面的*將只會匹配當前的行尾。雖然可能還會有很多。但在執行匹配的時候,*裏面已經是”所有收到的“了。除非模式充分考慮這些情況,要不然Expect不會知道還會有接下來的輸出。

把程序設成工作在行緩衝模式下也是不可取的。不僅程序很少把它們的緩衝模式固定,系統處理也可能把輸出分隔成多行的。而且分隔點可能在很隨機的地方。因此如果在確定模式的時候能夠描述出提示符的最近字符,建議你寫上它們。

如果你想匹配程序最後輸出的字符,但程序卻輸出了其他的字符,這樣你就不可能通過timeout模式來檢測到這種情況。原因是Expect不會有超時,相反它會得到一個“遇到文件尾”的信號。使用eof來代替timeout,或是兩者都用更好。Thatway if that line is ever moved around, you won't have to edit theline itself.

終端驅動輸出字符的時候一般會把輸出中的換行轉換爲回車,換行的順序。因此如果你想一個模式能夠匹配分別在兩行中的字符,例如printf(foo\nbar)的輸出,你需要把模式設爲"foo\r\nbar"。通過expect_user命令從用戶讀取信息時也會執行類似的轉換。這種情況下,當你按下回車,它將被轉換爲“換行”,如果這時Expect把它傳給一個工作在原始狀態的終端上的程序,會產生一個錯誤,因爲程序期望的是一個真正的回車(一些程序設計考慮並解決了這個問題,它會把“換行”轉換爲“回車”,但大部分不會)。不幸的是,沒有辦法檢測一個程序是否把它的終端設成原始狀態。

爲了避免手工將“換行”轉換爲“回車”,解決方法是使用sttyraw來阻止這種轉換。注意:這樣的話,你就不能使用“精加工”模式下的行編輯特性了。

Interact命令隱含的把終端設置成原始模式,這樣,上面的問題就不會在它裏面出現。

有時在Expect的腳本中存儲密碼(或其他個人信息)是很有用的。但不建議這樣做。因爲存在計算機上的東西很可能會受到其他用戶的影響。因此,從一個腳本中交互的提示輸入密碼要比把密碼嵌入到腳本內好一些。儘管如此,有時嵌入是唯一可行的辦法。

不幸的是,UNIX文件系統不能直接創建可執行而且不可讀的腳本文件。支持設置setgid的系統可以間接實現如下:創建Expect腳本文件(包含隱私的數據),設置它的權限位爲750(-rwxr-x---),把所有權賦給一個可信任的組。也就是一個可以讀取它的組。如果需要的話,可以創建一個這樣的組。接下來,以權限2751(rwxr-s--x)建立一個/bin/sh腳本文件,擁有它的所有權限的組和上一個文件一樣。結果就是可以被其他人讀和執行的腳本。在調用的時候,它執行的是Expect腳本。


發佈了48 篇原創文章 · 獲贊 4 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章