讓你的perl代碼看起來更像perl代碼,而不是像C或者BASIC代碼,最好的辦法就是去瞭解perl的內建變數。perl可以通過這些內建變數可以控製程序運行時的諸多方面。 本文中,我們一起領略一下眾多內建變數在檔案的輸入輸出控制上的出色表現。 行計數 我決定寫這篇文章的一個原因就是,當我發現很多人都不知道“$.”內建變數的存在,這的確讓我很吃驚。 我依然能看到很多人是這樣寫代碼的: 代碼 my $line_no = 0; while (<FILE>) { ++$line_no; unless (/some regex/) { warn "Error in line $line_no/n"; next; } # process the record in some way } 由於某些原因,很多人似乎完全忽略了“$.”的存在。而這個變數的作用就是跟蹤目前記錄號。因此上面的代碼也可以這樣來寫: 代碼 while (<FILE>) { unless (/some regex/) { warn "Error in line $./n"; next; } # process the record in some way } 譯者註:通俗的說,這個內建變數就跟資料庫中的記錄指標非常相似,它的值就是你當前所讀檔案中的當前行號。 雖然使用此內建變數並不能讓你少打多少字,但重要的是我們可以省去一些不必要的變數聲明。 另一種利用此內建變數的方法就是與連續操作符(..)一起使用。當用在列表上下文中時,(..)是列表構建操作符。它將從給出的開始和結束元素之間建立所有的元素。例如: 代碼 my @numbers = (1 .. 1000); @numbers將包含從1到1000之間所有的整數。 但是當你在一個運算式上下文中使用此操作符時(比如,作為一個聲明的條件),它的作用就完全不一樣了。第一個運算元(“..“左側的運算式)將被求值,如果得出的值為假,此次操作將什麼也不做並返回假值。如果得出的值為真,操作返回真值並繼續依次返回下面的值直到第二個運算元(“..”操作符右面的運算式)返回真值。 我們舉個例子解釋一下。假設你有一個檔案,你只想處理這個檔案的某幾個部分。這幾個部分以"!! START !!"為開始,"!! END !!"為結束。 使用連續操作符你可以這樣寫這段代碼: 代碼 while (<FILE>) { if (/!! START !!/ .. /!! END !!/) { # process line } } 每一次迴圈,連續操作符就會檢查當前行。如果當前行與“/!! START !!/”不匹配,則操作符返回假值並繼續迴圈。當迴圈到第一個與/!! START !!/”相匹配的行時,連續操作符就會返回真值並執行if語句塊中的代碼。在while語句後面的迴圈中,連續操作符將再次檢查“/!! END !!/”的匹配行,但是它直到找到匹配行後才會返回真值。這也就是說在"!! START !!" 和"!! END !!" 標記之間的所有行都將被處理。當找到/!! END !!/的匹配行後,連續操作符返回假並再次開始匹配第一個規則運算式。 這些與“$.”有什麼關係呢?如果連續操作符的運算元有一個是常量的話,他們將被轉化為整型數並於“$.”匹配。 因此輸出一個檔案的前10行內容我們可以這樣寫代碼: 代碼 while (<FILE>) { print if 1 .. 10; } 關於“$.”最後要說明的一點是,一個程式中只有一個“$.”變數。如果你在從多個檔案控制代碼中讀資料,那麼“$.”變數儲存了最近讀過的檔案控制代碼中的目前記錄號。如果你想要更複雜的解決此問題的方法那麼你可以使用類似IO::FILE對象。這些對象都有一個input_line_number方法。 記錄分隔字元 “$/” 和 “$/”分別是輸入輸出記錄分隔字元。當你在讀或者寫資料時,他們主要控制用什麼來定義一個“記錄”。 讓我更詳細地給大家解釋一下吧。當你第一次學習perl,第一次知道檔案輸入操作符的時候,也許你會被告知“<FILE>”就是從一個檔案讀 入一行資料,而讀入的每一行都包括一個新行字元(“/n”)。其實你所知道的這些並不完全是真的,那隻是一個很特殊的情況。實際上檔案輸入操作符(“<>”)讀資料後會包含一個在“$/”中指定的檔案輸入分隔字元。讓我們來看一個例子: 假設你有一個文字檔,內容是些有趣的引文或者一些歌詞或者一些別的什麼東西。比如類似下面的內容: 代碼 This is the definition of my life %% We are far too young and clever %% Stab a sorry heart With your favorite finger 在這裡有三段被一行“%%”分隔的引文。那麼我們該如何從這個檔案中一次讀取一段引文呢。(譯者註:這一段引文可是一行也可以是幾行,比如例子中的第一段和第二段引文都是一行,而第三段引文是2行) 其中一個解決方案就是,一次從檔案中讀取一行,然後檢查讀入的行是否是“%%”。因此我們需要聲明一個變數用來儲存每次讀入的資料,當遇到“%%”後重新組合先前讀入的資料為一段完整的引文。哦,你還需要記得處理最後一段引文因為它最後沒有“%%”。 這樣的方法太過於複雜,一個簡單的方法就是更改“$/”變數的內容。該變數的預設值是一個新行字元(“/n”),這也就是為什麼“<>” 操作符在讀取檔案內容時是一次讀一行。但是我們可以修改這一變數內容為我們喜歡的任意值。比如: 代碼 $/ = "%%/n"; while (<QUOTE>) { chomp; print; } 現在我們每次調用“<>”,perl會從檔案控制代碼中一次讀取資料直到發現 “%%/n”為止。(不是一次讀一行了)。 因此,當你用chomp函數來去掉讀取資料的行分隔字元時,就會刪除“$/”變數中指定的分隔字元了。在上例中經過chomp函數處理後的資料都會將 %%/n”刪除。 更改perl的特殊變數 在我們繼續之前,我需要提醒你的是,當你修改了這些特殊變數的值後,你會得到一個警告。問題就是這些變數中的多數是被強制在主包中 的。也就是說當你更改這些變數的值時,程式中用到這個值的地方(包括你包含的那些模組)都會給出警告。 比如如果你在寫一個模組,且你在模組中更改了“$/”變數的值,那麼當別人把你的模組應用到自己的程式中時就必須相應的修改其他模組 以適應程式的執行。所以修改特殊變數的值潛在地增加了尋找bugs的難度。 因此我們應該儘可能的避免它。第一個避免的方法是在你用完了修改後的特殊變數的值後應該將該特殊變數重值回原始值。比如: 代碼 $/ = "%%/n"; while (<QUOTE>) { chomp; print; } $/ = "/n"; 而這個方法引發的另一個問題就是你不能確定在你重設特殊變數的值之前它的值就是系統預設值。 (譯者註:比如如果你在“$/ = "%%/n";”之前就修改過“$/”變數的值(不是預設值“/n”),那麼你最後重設回預設值肯定會引發錯誤的) 因此我們的代碼應該像如下才對,如下: 代碼 $old_input_rec_sep = $/; $/ = "%%/n"; while (<QUOTE>) { chomp; print; } $/ = $old_input_rec_sep; 上面的代碼就避免了我們上述所說的bug,但是我們有另一個看起來更簡練的方法。這個方法就是使用local來定義“$/”變數。如下: 代碼 { local $/ = "%%/n"; while (<QUOTE>) { chomp; print; } } 我們將代碼以一對大括弧括起來。一般的,代碼塊往往與迴圈,條件或者是子程式有關聯,但是在perl中是可以單獨用大括弧來說明一個代碼塊的。 而在這個代碼塊內用local定義的變數只在當前代碼塊中起作用。 綜上所述,不更改perl的內建變數是一個很好的習慣,除非它被本地化在一個代碼塊中。 “$/”的其他值 下面給出一些你可以賦予“$/”變數的特殊值,這些值可以開啟一些有趣的行為。第一個就是設定該變數為未定義。這將開啟slurp模式, 開啟該模式後我們可以一次性從一個檔案中讀取全部的檔案內容。如下: 代碼 my $file = do { local $/; <FILE> }; 一個do語句塊的傳回值是語句塊中最後一個運算式的值,如上面的do語句塊的傳回值就是“<>”操作符的傳回值。而且由於“$/”變數被設定為 undef(未定義),所以返回的就是整個檔案的內容。需要注意的是,我們不需要明確地指定“$/”變數為undef,因為所有的perl變數在定義的時候就被自動初始化為undef。 設定“$/”變數為undef和空值是有很大區別的:設定成空值意味著開啟paragraph模式(即段落模式),在這種模式下,每個記錄就是一段以一個或更多空行為結束的文本段落。也許你會認為這種效果和把“$/”變數被設定為“/n/n”的效果是一樣的,但是他們還是有微妙的區別的。如果一定進行比較,那麼應該把“$/”變數設定成為“/n/n+”才能和paragraph模式相同。(注意,這裡只是比方說。實際上是不能將“$/”變數設定為規則運算式的)“$/”變數的最後一個特殊值就是可以將其設定為一個整數標量變數的引用或者是一個整數常量的引用。 在這種情況下,從檔案控制代碼中每次讀出的資料最多是“$/”變數指定的大小。(在這裡我說“最多”是因為在檔案的最後有可能剩餘的資料大小小於“$/”變數指定的大小)。因此,如果你想每次讀出2kb的資料那麼你可以這樣做: 代碼 { local $/ = /2048; while (<FILE>) { # $_ contains the next 2048 bytes from FILE } } “$/” 和 “$.” 注意到當改變“$/” 變數的值時候也相應的改變了perl對於記錄的定義因此也改變了“$.”變數的行為。“$.”變數實際上儲存的不再是當前“行”號了,而是當前的記錄號。因此在前述的那個引文的例子中,“$.”變數將按照你所要讀出資料的檔案中的每一段引文遞增。 關於“$/” 在前面的開始我提到了“$/” 和“$/”變數作為輸入和輸出的記錄分隔字元。但是我們一直沒有介紹“$/”變數。 說實話,“$/”並不像“$/”那麼有用。它包含了每次調用print輸出時在最後要增加的字串。 它的預設值是Null 字元串,因此當你用print進行輸出時,並沒有任何東西跟在輸出的資料後面。當然如果你非常希望能有個類似pascal的輸出函數println,那麼我們可以這樣寫: 代碼 sub println { local $/ = "/n"; print @_; } 這樣,在你每次用print輸出資料時都會在資料後面增加一個"/n"(即分行符號)。 其它 Print 變數 接下來的兩個需要討論的變數是非常容易混淆,儘管它們做的是完全不同的兩件事。為了舉例說明,看下面代碼: 代碼 my @arr = (1, 2, 3); print @arr; print "@arr"; 現在,如果不仔細地看你是否知道上面兩個print調用的區別嗎? 答案是,第一個print調用會緊挨著輸出數組的三個元素,其間沒有任何分割符(輸出為:123)。然而第二個print語句輸出的元素確實以空格為分隔的(輸出為:1 2 3)。為什麼會有此區別呢? 理解這個問題的關鍵就是,在每種情況下實際傳給print調用的是什麼。在第一種情況下,傳遞給print的是一個數組。perl將展開傳遞過來的數組為一個列表,列表中的三個元素被視為單獨的參數。而第二種情況下,在傳遞給print之前,數組被雙引號所包含。 確切地說第二種情況也可以理解成如下的過程: 代碼 my $string = "@arr"; print $string; 因此,在第二種情況看來,傳遞給print函數的只是一個參數。事實上的結果就是對一個數組進行了雙引號的包含,並不影響print函數是如何對待該字串的。 因此擺在我們面前的就是兩種情況。當print接收一組參數的時候,它將緊湊地將這些參數輸出而在輸出的參數之間沒有空格。當一個數組被 雙引號包含起來傳遞給print之前,數組的每個元素將以空格為分隔字元展開為一個字串。這兩種情況是完全不相干的。不過從我們上面舉的例子我們很容易看出人們是如何混淆這兩種情況的。 當然,如果我們願意,perl允許我們改變這種行為。“ $,”變數儲存了分隔傳遞給print函數的參數所用到的字串。正如上面介紹的,預設分割print參數的字元是Null 字元,當然這都是可以更改的: 代碼 my @arr = (1, 2, 3); { local $, = ','; print @arr; } 這段代碼將輸出1,2,3 相應地,當一個數組被雙引號包含傳遞給print函數時,展開這個數組後用來分割元素的字元則儲存在“$"”變數中。代碼如下: 代碼 my @arr = (1, 2, 3); { local $" = '+'; print "@arr"; } 這段代碼將輸出 1+2+3 當然,在一個print語句的使用中“$"”變數並不是必須的。你可以用在任何被雙引號包含的數組的地方。而且它也不僅僅是對數組才有效。 也可以用在雜湊表上。 代碼 my %hash = (one => 1, two => 2, three => 3); { local $" = ' < '; print "@hash{qw(one two three)}"; } 這將輸出: 1 < 2 < 3 http://bbs.chinaunix.net/thread-1191868-1-1.html# |