Perl学习笔记–Matching Principles

来源:互联网 发布:js函数柯里化 编辑:程序博客网 时间:2024/06/03 14:53

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.