Perl學習筆記–Matching Principles

Perl就像一把瑞士軍刀,臨戰時用起來很方便。特別在文本處理的時候,比如在大型系統中追蹤問題常常以來於系統日誌,我們有很多日誌,access_log或者err_log或者deploy_log。今天我一邊看劇場版的名偵探柯南,一邊看了一下Perl的正則表達式匹配的文檔,寫點筆記,也算這個週末沒有完全墮落啊。

先看一個小例子:

"abcde"=~/(abd|abc)(df|d|de)/;意思是匹配的部分有兩部分(two groups)組成,第一部分(group)有兩種可能(alternatives)分別是abd和abc,第二部分是df或者d或者de。那Perl內部在匹配的時候是經歷的怎樣一個過程呢?

  1. 從字符串的第一個字符a開始考察。
  2. 拿出正則表達式第一部分的第一個選擇abd試圖匹配。
  3. 前兩個字符ab,恩,so far so good。
  4. 可是abd中的d並不匹配abcde中的c。匹配失敗,回退(backtrack )2個字符,用正則表達式第一部分的第二個選擇abc來重新進行匹配。
  5. abc都匹配上了。正則表達式第一部分完成匹配。把$1設成’abc’。
  6. 現在從第二部分中取出第一種可能df。
  7. d匹配了。
  8. 可惜f和e不匹配。回退(backtrack )1個字符,拿出第二種可能d試圖匹配。
  9. d匹配上了,所以第二部分(group)匹配完成。設置$2=’d'。
  10. 我們已經到達正則表達式的結束位置,此次匹配全部完成了。我們在字符串’abcde’中匹配了子串’abcd’。

在Windows XP的cmd下面驗證一下:

C:>perl -e "print qq{$1,$2} if q{abcde}=~/(abd|abc)(df|d|de)/"

abc,d

這裏要注意幾點:

  1. 正則表達式第二部分的de也能匹配。但是在de之前的d已經能夠匹配了,de就沒有機會了。這裏有個原則,在給定位置的點,碰到用|分隔的多種可能性時,優先考慮左邊的選項(alternative)。誒,這句話我不知道怎樣翻譯比較好,還是給原文吧:at a given character position, leftmost wins!
  2. 稍許再解釋一下backtrack的概念。其實backtrack來自於這樣的想法:說正則表達式匹配的過程啊就像是在走迷宮。如果成功匹配呢,就是你成功的走出了迷宮,只有你嘗試了每一條可能的路徑都走不出去,才能說匹配失敗。假設你現在處在A點,你前面有好幾種選擇,走哪一條路呢,這種情況就是grouping裏面有多種alternatives的情況,比如(abd|abc)。Perl總是優先選擇leftmost path,這裏就是abd,並且成功的向前走了兩個格子(2 characters),當它發現第三個格子d不匹配的時候,就要回退到上一個成功的點,A點,這裏就是字符串開頭。這個回退的過程,就叫做backtrack。

現在我們知道在正則匹配的時候是有一些原則的,比如這裏的left most。那我們再看一個例子:

$x="The programming republic of Perl";

$x=~/^(.+)(e|r)(.*)$/;

意思是從字符串頭開始匹配1個或多個字符,然後碰到字符e或者r,然後後面跟着0個或多個字符。問題是^(.+)是匹配到programming中的r之前的部分,還是 republic中的r之前的部分,還是Perl中的e之前的部分,還是Perl中的r之前的部分呢??哈哈,好像比前面複雜了哦,這裏有4條黃金法則:

爲了不誤人子弟,我還是貼文檔裏面的英文吧-:)

  1. Taken as a whole, any regexp will be matched at the earliest possible position in the string.
  2. In an alternation a|b|c… , the leftmost alternative that allows a match for the whole regexp will be the one used.
  3. The maximal matching quantifiers ?, * , +  and {n,m} will in general match as much of the string as possible while still allowing the whole regexp to match.
  4. If there are two or more elements in a regexp, the leftmost greedy quantifier, if any, will match as much of the string as possible while still allowing the whole regexp to match. The next leftmost greedy quantifier, if any, will try to match as much of the string remaining available to it as possible, while still allowing the whole regexp to match. And so on, until all the regexp elements are satisfied.

我試圖以我的理解重新組織一下,在保證能夠匹配的前提下,總是試圖儘早的匹配,如果在給定位置,有多條路可走,優先走最左邊的路,選定了一條路了,就能走多遠走多遠。

再回來看剛纔的例子,儘早匹配,那第一個字符T就匹配上了,^(.+)是貪婪的,能走多遠走多遠,在滿足整體匹配的前提下,一直走到Perl中的r之前。然後(e|r)匹配字符r, (.*)$匹配字符l。

C:>perl -e "print qq{$1,$2,$3} if q{The programming republic of Perl}=~/^(.+)(e|r)(.*)$/"
The programming republic of Pe,r,l

現在我們來做些練習,$x還是等於"The programming republic of Perl":

$x=~/(m{1,2})(.*)$/;

儘早匹配,匹配點是programming中的第一個m,由於貪婪,在既能匹配1個m,又能匹配2個m的情況下,匹配2個m,最後(.*)$匹配剩餘的子串。$1=’mm’, $2=’ing republic of Perl’。

$x=~/.*(m{1,2})(.*)$/;

儘早匹配,匹配點是T,由於貪婪,一直匹配到programming中的第一個m,並且試圖匹配第二個m,但是他不能這麼做,因爲這樣m{1,2}就不能匹配了,別忘了我們的最高指示是整體匹配,是走出迷宮!於是m{1,2}只能匹配第二個m。最後 $1=’m', $2=’ing republic of Perl’。

$x=~/(.?)(m{1,2})(.*)$/;

(.?)匹配0個或者1個字符,也就是可以匹配programming中的a,也可以是第一個m,但是"儘早匹配"意味着匹配a,從字符a的下一個位置開始匹配m{1,2},因爲貪婪,所以要匹配兩個m。最後 $1=’a', $2=’mm’, $3=’ing republic of Perl’。

最後看一個例子,"zXXXb"=~/(X*)/;一看很簡單嘛,從第一個X開始匹配,因爲貪嘛,一直貪3個X,所以(X*)匹配’XXX’ 。可是我錯了!

C:>perl -e "print $1 if q{zXXXb}=~/(X*)/"

 

打出來的是空,空啊,難道是沒有匹配?其實我這裏就是忘掉了’儘快匹配’,忘掉了第一條matched at the earliest possible position in the string. 因爲(X*)的意思是0個或多個X,所以字符z之前的空串也被匹配了,這裏$1=空!這裏稍加改動,把(X*)改成(X+),就能看到區別:

C:>perl -e "print $1 if q{zXXXb}=~/(X+)/"

XXX

所以最後提醒一下,4條黃金法則中的第一條是最厲害的。我看書還是不夠認真,犯了錯纔看到了這樣一句話,就在黃金法則正下方:

As we have seen above, Principle 1 overrides the others — the regexp will be matched as early as possible, with the other principles determining how the regexp matches at that earliest character position.

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章