程式扎記: [Perl 學習手冊] CH08 : 以正規表示式進行比對

標籤

2010年9月4日 星期六

[Perl 學習手冊] CH08 : 以正規表示式進行比對


前言 :
前一章, 你已經知道了正規表示式是用來做什麼的. 接下來你將會知道如何在 Perl 的世界使用這把好用的屠龍刀.

以 m// 進行比對 :
在前面我們用雙斜線的寫法來撰寫樣式, 比如 /john/. 但事實上這是 m// (樣式比對) 運算符的簡寫. 除此之外我們還可以使用斜線以外的界定符, 就跟 qw// 運算符提到的一樣, 但這時前面的 m 就不可以省略了.
當然Perl 建議你選擇樣式中不會出現的符號來做為界定符號, 譬如你要建立一個樣式來比較一般網址開頭的樣式時, 你可能會寫出 /^http:\/\// 來比對起始的 "http" 但是如果你能選擇更好的界定符號. 例如 : m%^http//% , 則程式碼會容易閱讀許多.

選項修飾符 :
選項修飾符字母 (option modifier letter) 有時候稱為旗標 (flag). 它們可以整組附加在某個正規表示式結尾界定符號的右邊, 以改變正規表示式的預設行為.

- 以/i 來進行不區分大小寫的樣式比對
使用 /i 修飾符, 可讓你在進行樣式比對時不區分大小寫, 使得你能夠輕易比對 JOHN , John 或是 john :
  1. print "Wanna play game? ";  
  2. chomp($_=);  
  3. if(/yes/i) { # yes YES yEs etc match anyway  
  4.     print "Ok. Let's play!!\n";  
  5. }  
- 以 /s 來比對任意字符
在預設行為裡, 點號 (.) 無法比對換列字符, 這對大多數 "僅希望在單一列進行比對" 的樣式來說是合理的. 如果字串有含換列字符, 而你仍希望點號能比對它們, /s 修飾符可以達成這個目的. 它將會將樣式中的每個點號轉換成字符集 [\d\D] 所做的事, 也就是符合任意字符 (包括換列字符). 考慮代碼如下 :
  1. $_ = "I saw John\n down at the bowling alley\nwith Peter\nlast night.\n";  
  2. if(/John.*Peter/s) {  
  3.     print "Got it!\n";  
  4. }  
因為比對樣式的兩個名稱並不在同一列, 所以如果省略 /s 修飾符, 上述比對就會失敗.

- 用 /x 加上留白
加上了 /x 修飾符後, 就可以在樣式裡隨意加上空白, 目的是使它看起來比較容易閱讀與了解. 由於加上了 /x 之後, 樣式可以任意留白, 所以字面上的跳格字符與空白就不在有意義, 它們會被忽略掉. 如果還要比對空白與跳格的話, 就得在前面補上一個倒斜線字符, 不過直接寫 \s (或 \s* , 或 \s+) 還是比較常見之比對空白的寫法.
在 Perl 裡, 註解也算是一種空白, 所以只要加上 /x 我們甚至可以在樣式裡加上註解, 以指明其用途 :
  1. if(/John        # Name   
  2.     .*          # Any string  
  3.     Peter       # Name  
  4.     /sx) {  
  5.     print "Got it!\n";  
  6. }  
因為井字號代表註解的標記, 所以要表示真的井字號時, 就得寫成 \# 或 [#]. 另外在註解裡請小心別把界定字符也寫進去了, 不然就會出乎預期的結果.

定位點 :
進行樣式比對的時候, 一開始是對字串的開頭, 如果不相符就在比對的過程中會一直往後面 "飄"(float) 過去, 一路往字串後面比對下去, 看看其他地方是否符合, 你可以手動加入定位點, 讓樣式直接比對字串的某處.
脫字符號 (^) 是一個定位點, 用於標示字串的開頭, 而錢號 ($) 也是一種定位符, 用於標式字串的結尾. 因此 /^john/ 只會成功比對到位於字串最前端的 john ; 如果是 abcjohn 這個字串, 則會比對失敗. 而 /john$/ 也只會成功比對到位於字串最後面的 john, 如果是 johnabc 也照樣會比對失敗.
某些時候這兩個字符/定位點一起使用, 可以確保樣式可以相符於整個字串. 有個常見的實例是 /^\s*$/ , 這代表一個空白列. 但這個 "空白" 列可能包含若干個看不到的空白字符.

- 單字定位點
除了字串的兩端, 還有其它定位點. \b 是單字邊界定位點 (word-boundary anchor), 它所比對的是一個單字的頭尾兩端. 因此 /\bjohn\b/ 可以比對 john 這個單字, 這在文書處理器搜尋命令通常稱為 "整字比對" 模式. 不過這裡所說的單字並不是一般的英文單字, 而是由一組 \w 字符所構成的單字, 也就是由普通字母, 數字與底線符號所組成的單字. \b 定位點所符合的是一組 \w 字符的開頭或結尾.
下圖中, 每個單字下方會出現綠色線 : \b 會比對的位置則以箭號標示. 因為每個單字都會有開頭結尾, 所以箭號一定是偶數.


此處所謂的 "單字" 是指一連串的字母, 數字與底線符號, 也就是符合 /\w+/ 樣式的字串. 因為單字邊界定位點 \b 只比對每組 \w 字符的開頭或結尾, 所以每個箭號會指向綠色線的開頭或結尾. 單字邊界定位非常有用, 他保證我們不會意外地在 delicatessen 中找到 cat, 在 boondoggle 中找到 dog. 有時候你只會用到一個 "單字邊界定位點" , 像是用 /\bhunt/ 來比對 hunt, hunting 或 hunter, 可以排除 shunt ; 或是用 /stone\b/ 來比對 standstone 或 flintstone, 可排除 capstones.
而 \B 則是 "非單字邊界定位點" ; 它會符合任何不符合 \b 的位置. 因此樣式 /\bsearch\B/ 會符合 searches, searching 與 searched, 但不符合 search 或 researching.

繫結運算符 :
比對 $_ 只是預設的方式而以, 繫結運算符 (binding operator) =~ 用於告訴Perl 拿右邊的樣式來比對左邊的字串, 而非比對 $_ . 舉例來說 :
  1. my $some_other = "I dream of betty rubble.";  
  2. if($some_other =~ /\brub/) {  
  3.     print "There's the rub.\n";  
  4. }  
乍看之下, 繫結運算符看起來像某種賦值運算符. 其實它只是表示將左邊的對像使用右邊的樣式進行比對. (預設會使用 $_). 因為繫結運算符的優先次序相當高, 用來括住樣式測試運算式的圓括號並非必要, 因此下面兩列運算會是一樣的 :
my $likes_perl = ( =~ /\byes\b/i);
my $likes_perl = +~ /\byes\b/i; # 忽略圓括號

比對變數 :
到目前為止, 每當我們在樣式裡使用圓括號時, 都只是用來表示不同的樣式分組. 但圓括號同時也啟動了正規表示式處理引擎的記憶功能. 記憶功能指的是把圓括號裡的樣式所實際比對到的部分字串, 暫時記下來的能力. 而每一分記憶所儲存的是原字串中相符的部分, 而不是樣式.
因為比對變數 (match variable) 所儲存的都是字串, 所以它們都是存量變數 ; 在 Perl 裡它們的命名方式會像 $1 和 $2 這個樣子. 樣式裡的圓括號有多少對, 比對變數就有多少個. 而這些變數能夠取出字串裡的某些部分就是正則表示式強大的一個重要理由 :
  1. $_ = "Hello there, neighbor.";  
  2. if(/\s(\w+),/) {  
  3.     print "Got $1\n";  # 這個單字是 there  
  4. }  
你也可以一次用好幾份記憶 :
  1. $_ = "Hello there, neighbor.";  
  2. if(/(\S+) (\w+), (\S+)/) {  
  3.     print "Got $1 $2 $3\n";  
  4. }  
如果樣式的某個部分可能會比對空字串, 那麼它就會產生空的比對變數. 也就是說比對變數的內容可能是空字串 :
  1. my $dino = "I got 1000 dollars";  
  2. if($dino =~ /(\d*) dollars/) {  
  3.     print "Got $1\n";  # 1000  
  4. }  
  5. my $dino2 = "I got thousands dollars";  
  6. if($dino2 =~ /(\d*) dollars/) {  
  7.     print "Got $1\n";  # 空字串  
  8. }  
- 記憶的持續性
這些 "比對變數" 的內容, 通常會維持到下一次樣式比對成功. 也就是說失敗的比對會保留前次成功的記憶內容! 然而成功的比對會將它們重設. 換句話說 "比對變數" 只應該在比對成功時使用 ; 不然你會得到某個樣式的記憶內容. 底下是個不良示範 :
  1. $test = "wrong";  
  2. $test =~ /(\w+)/;  # 故意先讓 $1 存入 wrong 字串  
  3. $wilma = "";  # 空字串  
  4. $wilma =~ /(\w+)/; #錯誤示範! 未檢驗比對結果.   
  5. print "Wilma 裡的字串式 $1... Really?\n";  
而這是樣式比對總是出現在 if 或 while 條件運算式裡的一個原因.

- 不具記憶功能的圓括號
講到現在你已經知道圓括號具有記憶功能, 將會把樣式比對到的部分存入比對變數, 但如果你不需要記憶的功能, 而只是想進行分組而以呢? 我們總是會寫出如下的樣式, 其中一部分字串是可有可無, 而另一部分則是我們想要記憶的. 舉例來說 "bronto" 這個字串是可有可無, 但接著在撰寫樣式時, 則必須要用圓括號將它括住, 方能表示這一個字串是可有可無. 接著利用一個比對寫出用於取得 "steak" 或 "burger" 的樣式 :
  1. $_ = "saurus steak";  
  2. if(/(bronto)?saurus (steak|burger)/) {  
  3.     print "John want a $2\n";  
  4. }  
即使 "bronto" 沒有出現在字串裡, 該部分樣式的比對結果還是會存到 $1 裡. Perl 是以左圓括號的順序來決定相應的比對變數. 於是我們想要的結果就被存到第二個比對變數 $2 去了. 不過 Perl 的正規表示式引擎也有不存取比對變數的圓括號. 我們稱之為不具記憶功能的圓括號 (non-capturing parentheses), 而撰寫時則是要加上一些特殊的標記. 此標記寫法就是在圓括號後面加上問號跟冒號 (?:) . 只要這麼寫, Perl 就知道這對圓括號只是用來進行分組, 接著我們可以修改上面範例來使用不具記憶功能的圓括號 :
  1. $_ = "saurus steak";  
  2. if(/(?:bronto)?saurus (steak|burger)/) {  
  3.     print "John want a $1\n";  
  4. }  
- 具名比對變數
你可以使用圓括號來記憶字串的某些部分, 然號使用數字變數 $1, $2 等取得所比對到的部分. 即便是簡單的樣式, 追蹤這些數字變數以及其中應該儲存哪些內容常常使人困惑 :
  1. my $names = 'John or Peter';  
  2. if($names =~ m/(\w+) (and|or) (\w+)/){  
  3.     say "I see $1 and $2\n";  
  4. }  
在上面例子會比對成功, 不過拿到的結果卻不是我們想要的 (John Peter). 那是因為我們加入了另外一組具記憶功能的圓括號 (and|or) 來比對 and 或者 or 字串. 因此預期的 Peter 會被存在 $3 中. 儘管我們可以使用不具記憶功能的圓括號來解決此問題, 但真正問題是我們必須記住哪組記憶圓括號相對應於哪個比對部分.
為了免除我們記住數字 ($1) 的麻煩, Perl 5.010 允許我們在正規表示式中直接對比對變數標記名稱. 它將所比對到的文字存入名為 %+ 的雜湊 : 雜湊鍵就是我們使用的標記名稱, 而相應的值就是 "字串中" 它所比對到的部分. 我們使用 (?
This message was edited 8 times. Last update was at 27/08/2010 10:27:39

沒有留言:

張貼留言

網誌存檔