您是一名“系統程序員”— 您編寫代碼以保持服務器正常運轉,並且為您的應用程序開發人員同事提供所需的底層功能。您從哪裡獲取所需的信息呢?大多數編程參考大全關心客戶機或者“應用程序”問題,而管理書籍通常回避編程而致力於“配置”。 我希望您會發現這一新的“服務器診所”專欄是有用的來源之一。每個月,我都將解決在服務器的“維護與支持”中遇到的一個編程問題或一類共同問題。 專欄第一部分將 EXPect 作為您最應該了解的一種語言進行介紹。您可能已經熟悉 Expect 了。不過,您也可能從未見過 Expect 所管理任務的完整范圍。Expect 實現了一種 Linux 系統編程的通用性,其它語言 — 即使是 C、Java 或 bash — 都無法與之相比。雖然未來的專欄文章將展示使用各種語言的解決方案,但 Expect 很可能是出現頻率最高的一個。
在 Tcl 上構建 什麼使 Expect“通用”呢?首先,應了解 Expect 是 Tcl/Tk 編程語言的適當超集。Tcl 是在各種程序中使用的一種高級語言。它過去通常與 Perl、Python、Ruby 和其它語言一起被歸為“腳本編制”語言。在 2002 年,最明智的做法是拋開某些歷史事件,簡單地將所有這些語言視為高效率的開放源碼語言。Tcl 在計算機輔助設計(CAD)領域中特別流行,象 Cisco 和 Nortel 這樣的聯網設備供應商也都使用它。與其它“腳本編制”語言一樣,Tcl 的內置功能適用於文本處理、數據庫管理、聯網和算法等領域中的最常見問題。 Tcl 是 Expect 的基礎。任何 Tcl 程序都自動是 Expect 程序。因為有下面兩個原因,所以強調這一點很重要: 許多人只知道 Expect 是一種“工具”,而從不了解它是一種完全成熟的編程語言。 1994 年,許多真正認識到 Expect 的通用能力的程序員都被它迷住了。 Expect 的作者是(美國)國家標准與技術協會(National Institute of Standards and Technology)的 Don Libes。他在 1994 年出版了一本關於 Expect 的出色書籍。該書現在仍只有第一版,它沒有競爭者;這本書寫得太好了,以至於沒有出版商出版另一本書。最引人注目的是,Exploring Expect(請參閱本文後面的參考資料)一直不需要更新。它的清晰和精確很好地經受了時間的考驗。 這裡的問題是,過去八年以來,Expect 的底層 Tcl 基礎已經有了極大發展。最初編寫 Expect 時,Tcl 並不追求成為通用的編程語言。從那時起,Tcl 已經: 知道如何處理完整的八位數據,甚至能方便地處理 Unicode; 添加了方便的 TCP/IP 抽象; 獲取了數據和時間計算以及格式化方面的能力; 改進並合理化了其字符串處理; 因此,請記住:如果 Perl、Java 或 C 可以解決一個問題,那麼 Tcl 以及 Expect 很可能也可以解決。 Tcl 有一項任何其它編程語言都“無與倫比(out of the box)”的工作,這就是圖形用戶界面(GUI)的構建。雖然從 ActiveState Tools Corporation 下載的 Linux 版標准 ActiveTcl 二進制分發版只有大約 10 兆字節,但它不僅包含 Expect,而且還包含功能齊全的集成 GUI 工具箱。下面的示例將說明這個名為“Tk”的工具箱如何簡潔地表達 GUI 解決方案。
難題的獨特解決方案 Expect 的 Tcl/Tk 基礎適用於范圍非常廣的編程。請記住,Expect 可以完成 Tcl/Tk 所能做的一切。除此之外,Expect 添加了三大類別的附加功能: 擴展的調試選項 描述面向字符對話框的便利命令 棘手的面向字符終端的獨一無二的管理 這些功能中第一個是常規的。Expect 有各種“開關”來記錄或報告其操作的各個方面。 Expect 的用途是使面向字符的交互自動化。您可能已經自己完成了許多這種工作。每次編寫命令行管道或重定向輸入/輸出(I/O)流時,您都在讓計算機管理這些工作,否則您必須自己輸入。 Expect 以兩種方式深化了這一控制:首先,它提供了表達對話框復雜程度的語言。Expect 不只使用固定“腳本”作為應用程序的輸入,而是使交互的每個擊鍵都可編程。 正如 Libes 所說,更關鍵的是:“最終,Expect 是為處理蹩腳的界面而設計的工具。”特別是 Expect 具有管理抵制 I/O 重定向的應用程序的能力。典型示例是命令行 passwd 程序。每個負責管理服務器的人員遲早都需要使密碼更新自動化。第一次嘗試可能是作為 root 用戶運行類似下面的代碼: 失敗的 passwd 自動化 passwd $user << HERE $newpassWord $newpassword HERE 正如每個嘗試它的人很快會發現,這根本不起作用。shell 的 < 和 << 重定向對於象 passwd 這樣的程序不起作用。 但是,Expect 可以使重定向起作用。Expect 知道如何與所有面向字符的應用程序對話,即使是象 passwd 那樣操縱終端設置的應用程序。 正是這一點完善了 Expect 的通用性。原則上,其它語言或庫可以提供終端特征的信息。例如,Perl 的 Expect.pm 模塊在這方面已經做了很多。雖然經過十多年生產使用,但卻沒出現其它有力的競爭對手。 這就是您應該學習 Expect 的原因。您將處理帶有“蹩腳界面”的程序 — 您周圍有很多這樣的程序 — 而 Expect 通過讓它們完成您所需的工作,可以減少幾小時甚至幾天的開發時間。同時,還可以將 Expect 用於通常由 bash 或 Perl 完成的所有作業。
有關 Expect 的所有其它須知信息 您還應該了解有關 Expect 的其它信息。本專欄的最後部分包括對 Expect 局限的說明、對解決常見問題的 Expect 工作代碼的概述以及可以引導您更深入了解 Expect 編程的參考。 Expect 所做的比大多數人所認識到的要多;這就是本專欄的主題。Expect 也有不足之處。系統程序員通常需要使象 FTP 操作、電子郵件發送或處理以及 GUI 測試這樣的任務自動化。對於其中的前兩項,Expect 無法提供幫助。更准確地說,雖然可以使用 Expect 來使 FTP 和電子郵件自動化(這樣做在前幾年也很常見),但是現在 Expect 在這些領域方面沒有特別優勢。其它語言和方法與面向 Expect 的編碼功效相同,或者更勝一籌。這個“服務器診所”專欄的未來部分將說明簡便聯網自動化的示例。 Expect 的著名用法是用於測試。Expect 是用於幾個高端產品(包括 gcc)質量控制中使用的 DejaGnu 系統的基礎。然而,雖然 Expect 可用於構建 GUI,並且在幾個測試框架中也很關鍵,但是通常 Expect 在用於 GUI 系統的測試框架中不起作用。 暫時回到上面提到的 passwd 問題。Expect 對它的展望是什麼呢? 要了解 Expect 源代碼,目前更簡便的做法是忽略安全性考慮事項。下面的程序需要作為 root 用戶運行。Expect 提供有用的功能以實現更安全的操作;不過在掌握 Expect 基礎知識後更容易理解這些。 您已經知道簡單 I/O 重定向對 passwd 不起作用。何種 Expect 程序提供了更好的結果呢? 更新密碼的簡單 Expect 程序 # Invoke as "change_password <user> <newpassword>". package require expect # Define a [proc] that can be re-used in many # applications. proc update_one_password {user newpassword} { spawn passwd $user expect "password: " exp_send $newpassword\n # passwd insists on verifying the change, # so repeat the password. expect "password: " exp_send $newpassword\n } eval update_one_password $argv 這就是 Expect 用來使程序自動化所需的全部代碼,其它語言幾乎不可能做到。再多用幾行,您可以一次對成百上千用戶進行批處理更新。這是一種常見需求;我經常被請去恢復密碼文件被嚴重毀壞的服務器,這裡說明了一種開始的方法: 簡單迭代 ... set default_password lizard5 set list [exec cat list_of_accounts] foreach account $list { update_one_password $account $default_password puts "Password for '$account' has been reset." } 同樣適用於 GUI 給 Expect 自動化加上 GUI 外觀也只需要多加幾行。假定您想為一名非程序員提供方便地更新密碼的應用程序。同樣忽略安全性考慮事項,這就象完成下列代碼一樣簡單: 簡單迭代 ... package require Tk frame .account frame .password label .account.label -text Account entry .account.entry -textvariable account label .password.label -text Password # Show only '*', not the real characters of # the entered password. entry .password.entry -textvariable password -show * button .button -text "Update account" -command { update_one_password $account $password } pack .account .password .button -side top pack .account.label .account.entry -side left pack .password.label .password.entry -side left 這個小工作應用程序具有下面的視覺外觀: 簡單 Expect 密碼管理器的抓屏