層級: 初級 Teodor Zlatanov (tzz@iglou.com), 程式員, Gold Software Systems 2000 年 10 月 09 日
如果您使用手工構建的方法,那麼基於檔案的配置很快就會崩潰。Teodor Zlatanov 示範了 AppConfig 模組如何處理用於 Perl 程式的本地配置儲存,以及如何將這些配置儲存到資料庫中,以便隨後能從網路上的任何機器進行訪問。
程式(從低級的列目錄程式到 網頁瀏覽器)的首要需求之一是:它應該是可配置的。事實證明,基於檔案的可配置性和命令列選項的組合是針對可配置性需要的長期而又靈活的解決方案。Perl 程式通常採用這種方法,儘管它們往往還包括一個設定檔和命令列解析常式。 我們將在本文中使用的命令列解析有一點複雜。因此,為了避免進一步的混淆,如果您進行中的解析等級高於簡單參數,我建議您使用 Parse::RecDescent(或等價的解析模組)。有關複雜命令列解析的討論,請參閱我關於說英語的 Perl 程式的 前一篇文章。 在開始之前,請確保您已經在系統上安裝了 Perl 5.005(或更新版本)和 CPAN AppConfig 模組。您還需要 Persistent::MySQL 或適用於您的特定資料庫的 Persistent 類。這些都可以在 CPAN 中擷取(請參閱本文後面的 參考資料)。 簡單的方法:自己動手(DIY) 理論上(並在有適當工具的情況下!)任何人都可以構建配置解析器,對嗎?舉例來說, Perl Cookbook展示了一個提供良好開端的快速實現。那麼,如果您從此類實現開始的話,編寫一個設定檔解析器有多難呢? 實際上相當困難,因為此類項目涉及如下幾個比較複雜的問題:
- 設定檔中的空白行和注釋
- 錯誤的行(象拼錯的關鍵字)以及哪些內容不可或缺而哪些內容可以忽略的問題
- 您必須自己編寫解析器的可能性,因為您可能需要多種不同資料結構(例如,布爾型、標量、數組和散列)
- 多個設定檔
- 變數預設值
- 將命令列選項與檔案配置整合並控制它們的互動方式
- 用另一種 DIY 設定檔格式培訓使用者(這通常有些類似於:“只要別一行中只有‘=’號,這就有效。哦,還有注釋由‘#’開始,但它們必須獨立於其它行。別忘了對關鍵字使用大寫,對值用小寫。回來!回來!我還沒有告訴您關於強制關鍵字的事情呢!”)
- 重新編寫或複製可能有錯誤的配置代碼而不是重用模組
- 使配置成為具有一致介面的對象而不是通常的關鍵字的 DIY 隨機散列
您已經害怕了?這就是我們使用 AppConfig 的原因。它可以處理所有這些問題。很清楚的一點是,您不應該使用 DIY。
求助於 AppConfig 儘管 Andy Wardley 編寫的 AppConfig CPAN 模組有助於解決上面列出的所有問題,但它不是萬能的。它不可能魔術般地改進您的程式。有時需要重新編寫以使用 AppConfig。(也存在一點學習曲線,本文試圖協助您降低學習的難度。) 顯然,如果您不確定應該使用 DIY 還是 AppConfig,那麼應該根據您的經驗和正在編寫的內容作出決定。但我相信,AppConfig 不能做得象 DIY 一樣好或更好這種情況是非常少的。 關於 AppConfig 能為您做些什麼,以下將逐點進行說明(按照前一節的問題列表):
- 設定檔中的空白行和注釋:AppConfig 可以識別空白行和注釋,並將忽略它們。
- 錯誤的行(象拼錯的關鍵字)中哪些內容是不可或缺的,哪些內容是可以忽略的:可以設定 AppConfig 的靈敏度,以忽略錯誤設定或異常中止程式。如果可以使用其它的拼字,則關鍵字可以有別名(譬如在國際化設定中)。
- 編寫自己的解析器,因為您需要不同的資料結構(布爾型、標量、數組)和散列:AppConfig 處理所有這些資料結構,但它這樣做時不用嵌套。如果您需要嵌套的散列或數組,則需要自己動手或協助一下 AppConfig。
- 多個設定檔:AppConfig 將根據需要處理任意數量的設定檔,依次從每個檔案中裝入設定。您還可以協助 AppConfig 重新設定數組和散列,以便插入到堆棧底部的值不必出現在堆棧頂部。
- 變數預設值:AppConfig 提供變數預設值。“-variable”文法將設定檔中的變數重新設定成其預設狀態。
- 控制命令列選項並將它們與檔案配置整合:AppConfig 提供對 Getopt::Std 和 Getopt::Long 命令列選項解析的支援。解析可以在讀取設定檔之前或之後執行。
- 用另一種 DIY 設定檔格式培訓使用者:AppConfig 使用標準、靈活的格式。“KEYWORD 值”和/或“KEYWORD=值”對於標量而言都是可接受的。因為數組是由元素構成的,所以“ARR=1”的後面跟著“ARR=2”將產生一個具有元素 1 和 2 的 ARR 數組。您還可以將布爾選項指定為“bool”、“nobool”、“!bool”、“bool on”、“bool off”、“bool yes”(但是“bool no”會產生錯誤)、“bool=1”、“bool=0”。(顯然,“聲名狼籍的”符號邏輯發明者 George Boole 博士 — 請參閱 參考資料 — 會覺得這些選項很親切。)散列選項被指定為“KEYWORD PARAMETER = 值”,其中的值將成為帶有關鍵字 PARAMETER 的散列項。
- 重新編寫或複製可能有錯誤的配置代碼,而不是重用模組:AppConfig 對這一點相當穩定,並且到該模組的介面不大可能更改。它還已經通過了數千名其他程式員的測試,所以為什麼不使用它呢?
- 使配置成為具有一致介面的對象,而不是使用通常的 DIY 隨機散列:一致的 API 從主程式中抽取配置處理常式,並用簡化與處理常式的串連(在本例中處理常式是 AppConfig)。這種方法引入的錯誤也更少,因為它只使用方法,而不直接使用資料結構。
既然我們已經看到了很多選用 AppConfig 的好理由,那麼,讓我們看一下完整的帶注釋的 AppConfig 用法樣本。目前,我們將省略許多比較進階的特性(將在下一節中討論)。可以從命令列用“-varname value”設定標量、布爾型和陣列變數,用“-varname key=value”設定散列變數。此處用的設定檔是 config.pl,以下是樣本:
# blank lines are ignored # set a booleandebug # set a scalarname=E.T. # set an arrayhosts = dbhosthosts = backuphost # reset the hosts array-hosts # add new values to hostshosts = firewallhosts = farewell # set a hashphone joe = 222-333-4444phone marge = 555-666-7777 |
AppConfig 進階用法 AppConfig 可以在幾個層級上進行變數擴充,這取決於 EXPAND 設定。有關更多詳細資料請參閱 AppConfig 文檔。
# expand all variables, globallymy $config = AppConfig->new({ GLOBAL => { EXPAND => EXPAND_ALL } }); # expand just HOME_DIR as UID, so "~username" will work as in the shell$config->define('HOME_DIR => { ARGCOUNT => ARGCOUNT_ONE, EXPAND => EXPAND_UID }); |
INI 樣式的節是 AppConfig 的另一個特性,您會發現它很有用。通過在設定檔中使用 [節](它本身佔一行),您可以列出在檔案結束前使用的所有關鍵字,也可以使用節名加上底線‘_’列出本節和下一節的所有關鍵字。例如:
location = /tmptype = txtname = accounts.txt[database]host = wyrmuser = slayerpassword = amethyst |
等價於:
file_location = /tmpfile_type = txtfile_name = accounts.txtdatabase_host = wyrmdatabase_user = slayerdatabase_password = amethyst |
可以用 varlist() 函數檢查 AppConfig 設定物件。下面的代碼列印了 AppConfig 對象中每個變數的內容。註:varlist() 可能有點麻煩,因為它必須採用Regex(Null 字元串是絕對無效的)。
use Data::Dumper; # for hash and array references my %varlist = $config->varlist('.*');foreach my $varname (keys %varlist){ print "Variable name $varname, value = ", Dumper $config->get($varname), "/n";} |
AppConfig 中有一個 Getopt::Long 介面,它允許訪問 Getopt::Long 模組的所有功能。下面的代碼定義了 Getopt::Long 的變數參數,調用該段代碼以解析來自命令列的參數。無效值會引起錯誤。
$config->define("help|h|!"); # define a boolean$config->define("code|c|=i"); # define a scalar integer$config->define("list|l|=f@"); # define a array of floating point values only$config->define("uids|u|=f%"); # define a hash of floating point values only $config->getopt(); # instead of args(), to use the Getopt::Long options |
AppConfig 中還可以進行變數驗證。這意味著通過引用Regex(或者甚至是一段代碼),變數會拒絕將其值設定為某些惡意的值或完全無意義值的嘗試。
# the username validation succeeds only when it is exactly "joe"# the password validation succeeds when it contains "joe" or "joE"$config->define( 'USERNAME' => { ARGCOUNT => ARGCOUNT_ONE, VALIDATE => sub # subroutine validation { my $varname = shift @_; my $value = shift @_; print "$varname = $value/n"; return ($value eq "joe"); } }, 'PASSWORD' => { ARGCOUNT => ARGCOUNT_ONE,VALIDATE => "jo[Ee]" # regex validation } ); |
AppConfig 使自動觸發操作成為可能,因此每次變數的值更改時,該操作就會執行。註:對 AppConfig 的引用也被傳送到子常式,所以單個更改會觸發其它變數更改。
$config->define( 'USERNAME' => { ARGCOUNT => ARGCOUNT_ONE, ACTION => sub # autoaction { my $config = shift @_; my $varname = shift @_; my $value = shift @_; print "$varname = $value/n"; } } ); |
AppConfig 限制 AppConfig 不處理變數中嵌入的代碼。在我看來,設定檔中無論如何都不應該存在代碼,並且允許使用者執行任意代碼是個壞主意。但是,AppConfig 不提供對變數的自動求值,儘管確實可以協調與變數相關聯的驗證和自動操作子常式來執行這項任務。如果您確實感到對此有強烈的需求,那麼挑出有疑問的變數並自己針對它們運行 eval()(以下面所闡述的方法)。不用說,除非您完全希望將對您的程式的這一層級的控制權賦予使用者,否則就 不要這樣做。
foreach my $varname ('username', 'password'){ $config->set($varname, eval $config->get($varname));} |
AppConfig 中 INI 樣式的節不是很重要。它們定義代碼節,但在使用之前必須預Crowdsourced Security Testing道這些節。最好先把節設計好,以便它們建立嵌套在父物件中的新 AppConfig 對象,但這是個次要問題。 在簡單測試中,使用 AppConfig 似乎不影響裝入和執行速度。它是個相當小的模組,其大小/速度代價通常可以忽略不計。當然,如果您的程式對時間很敏感,那麼您應該針對使用和不使用 AppConfig 的情況分別對它計時,然後自己決定是否值得使用該模組。 AppConfig 的複雜程度和學習曲線之所以比您原來預期的要低很多,很大程度上是因為出色的 API。這裡有使人混淆的地方(尤其對新程式員更是如此),但總的來說,對於任何原先具有 Perl 經驗的人,這不是很重要的問題。 AppConfig 的解析限制在於其可用性。如果您需要命令列選項的進階解析,請參閱有關說英語的 Perl 程式的 前一篇文章。(這一限制與 AppConfig 不能進行上下文敏感的解析有特殊的關係。)
用 AppConfig 和 Persistent::DBI 將配置上傳到資料庫 我建議在閱讀本節之前,閱讀我關於 用 Persistent 模組儲存資料的文章。您還應該對 Perl 引用和 SQL 資料庫有一定理解。我特定的程式碼範例使用了 MySQL 資料庫以及相應的 Persistent 模組。如果您正在使用另一種資料庫(例如 Postgres 或 Oracle),則應該尋找其它 Persistent 模組。 在配置可用於資料庫環境之前,必須先設計資料庫模式。換句話說,在開始編寫用來儲存和恢複資料的代碼 之前,您需要確定希望儲存什麼資料。這個樣本將在不同表中儲存布爾值、標量、數組和散列。 這未必是最佳途徑。您也可以將一個表用於所有資料類型,或按用途劃分表。我的樣本僅是實現持久配置的許多方法之一。它肯定不是僅有的方法。 我在此處提供的模式可能對於大多數用途都足夠了。它確實對於值和鍵長度有一些限制,但可以在代碼中輕鬆地調整那些限制。但是,建立數組和散列元素標識的方法可能引起問題。在這些情況下沒有完美的解決方案,只有解決問題的不同方法。將任意的結構化資料存放區到關聯式資料庫總是麻煩的。 我們將使用 AppConfig::State 中的 _argcount() 方法。有關這個方法的更詳細資料,請閱讀 AppConfig::State 的手冊頁。簡單來說,如果我們知道變數名,該方法就可以告訴我們正在處理什麼類型的變數。 在My Code樣本中,我使用了 MySQL 資料庫和相應的 Persistent 模組: persistent-config.pl。
結束語 只要做少量工作,就可以使 AppConfig 和 Persistent 類很好地合作。前一節中展示的持久配置指令碼可以處理大多數具有短鍵和變數名的配置,但還有可改進的餘地。在按自己的喜好編寫完指令碼後,就可以在網路的 任何地方啟動它,並讓它從中央主機載入當前配置。至少,進行改進將教會您關於資料庫配置的知識,並有助於您以網路為中心的新方式研究應用程式。 代碼重用通常是模組唯一最大的好處,尤其對 AppConfig 更是這樣。對於 DIY(自己動手)方法產生了錯誤和延遲的情況,AppConfig 提供了有效單一解決方案,該方案很可能可以滿足大多數配置需求。 “AppConfig 限制”一節所列出的限制非常少。當然,在選用 AppConfig 之前,您應該確定對於您的項目什麼才是最適合的。最好牢記如何將此處提供的資訊應用於您的特定項目,並研究 AppConfig 手冊頁。 現在幹什麼呢?您應該從 AppConfig 中只採用您所需要的。而不要嘗試讓它為您做所有的工作。將一半程式放到一個設定檔中看來挺有趣,但如果這樣做,不久以後使用者就會咆哮著衝進您的辦公室。請使您的設定檔有邏輯性而又簡單。編寫程式接受的配置文法的詳細說明,包括 AppConfig 提供的非常周到的命令列選項。 |