2007年03月18日 星期日 12:50
9.3.1原则1
正则表达式有三种形式:匹配、替换和转换。 在表 9-1 中列有三种正则表达式运算符。 接下来对每一个表达式给出详尽解释。 匹配:m/<regexp>/这种形式表明在//内部的正则表达将用于匹配 = ~或 !~左边的标量。为了语法上的简化用/<regexp>/,略去m。 替换:s/<regexp>/<substituteText>/这种形式表明正则表达式<regexp>将被文本 <substituteText>替换,为了语法的简化用/<regexp>/<substituteText>略去s。 ·转换:tr/<charClass>/<substituteClass>/这种形式包含一系列的字符—/<charClass>—同时把它们替换为<substituteClass>。 注意转换<tr>并不真正是一个正则表达式,但是对于用正则表达式难于处理的数据常使用它来进行操纵。因此,tr/[0-9]/9876543210.组成1223456789,987654321等字符串。 通过使用=~(用英语讲:does,与“进行匹配”同)和!~(英语:doesn't,与“不匹配”同)把这些表达式捆绑到标量上。作为这种类型的例子,下面我们给出六个示例正则表达式及相应的定义: $scalarName =~ s/a/b; # substitute the character a for b, and return true if this can happern $scalarName =~ m/a; # does the scalar $scalarName have an a in it? $scalarName =~ tr/A-Z/a-z/; # translate all capital letter with lower case ones, and return ture if this happens $scalarName !~ s/a/b/; # substitute the character a for b, and return false if this indeed happens. $scalarName !~ m/a/; # does the scalar $scalarName match the character a? Return false if it does. $scalarName !~ tr/0-9/a-j/; # translate the digits for the letters a thru j, and return false if this happens. 如果我们输入像 horned toad =~ m/toad/ 这样的代码,则出现图 9-1 所示情况: 另外,如果读者正在对特定变量 $_ 进行匹配(读者可能在while循环,map或grep中使用),则可以不用!~和=~。因而,以下所有代码都会合法: my @elemente = (' al' ,' a2' ,' a3' ,' a4' ,' a5' ); foreach (@elements) {s/a/b/;} 程序使 @elements 等于b1,b2.b3,b4,b5。另外: while(<$FD>) {print if (m/ERBOR/);} 打印所有包含 error 字符串的行: if (grep(/pattern/, @lines)) {print " the variable /@lines has pattern in it!/n";} 打印所有包含模式pattern内容的行,这直接引入下一原则。 9.3.2 原则2 正则表达式仅在标量上匹配。 注意这里标量的重要性,如果读者试一试如下代码: @arrayName = (' variablel', 'variable2'); @arrayName =~ m/variable/; # looks for ' variable' in the array? No! use grep instead 那么@arrayName匹配不成功! @arrayName被Perl解释为2,于是这意味着读者在输入: ' 2' =~ m/variable/; 至少讲这不能给出预想的结果。如果读者想这样做,输人为: grep(m/variable/, @arrayName); 该函数通过@arrayName中的每一个元素进行循环,返回(在标量环境中)匹配的次数,同时在数组环境中返回匹配元素的实际列表。 9.3.3 原则3 对于给定的模式串,正则表达式只匹配最早出现的匹配项。匹配时缺省一次只匹配或替换一次。 这个原则使用称为“回溯”的过程指出如何匹配一个给定的字符串;如果发现了一个局部匹配进而找到使该匹配无效的东西,正则表达式在字符串中“回溯”最小的可能数量,这个数量的字符要保证不丢失任何匹配。 对于理解正则表达式正在做什么,这个原则是最有帮助的一个,同时不需要与Perl一样的形式来理解它正在做什么。假定有如下模式: ' Silly people do silly things if in silly moods' 同时想匹配如下模式:‘ ' silly moods' 那么正则表达式引擎匹配silly,接着遇到people的P,至此,正则表达式引擎知道第一个silly不匹配,于是正则表达式引擎移到 P 且继续寻求匹配。它接着遇到第二个silly,于是来匹配moods。然而得到的是字母 t(在thing中),于是移到 things中的 t 处,继续进行匹配。当引擎遇到第三个silly并且尽力匹配moods时,匹配成功,匹配最后完成。所发生的情况如图 9-2 所示。 当我们遇到通配符时回溯将变得更加重要。如果在同一正则表达式中有几个通配符,且所有的通配符交织在一起,那么这里就有病态情形出现,在这种情形下,回溯变得非常昂贵。看如下表达式: : $line = m/expression.*matching.*could.*be.*very.*expensive.*/ .* 代表一个通配符,它意味着“匹配任意字符(换行符除外)零次或多次”。这个过程有可能花很长时间;如果在未匹配过的字符串末尾有可能匹配,那么引擎将发狂地回溯。为得到这方面的更多信息,请留意关于通配符方面的原则。 如果读者发现类似于上面的情形,那么通配符需将正则表达式分解成小功部分。换句话讲,简化自己的正则表达式。 9.3.4 原则4 正则表达式能够处理双引号字符串所能处理的任意和全部的字符。 在s///运算符(s/*//),或者m//运算符m/*/的第一个分隔区,位于其中的条目确实能像双引号字符串一样对待(带有一些额外的附加功能,名义上的特殊正则表达式字符!后面描述)。读者可用他们进行内插: $variable = ' TEST' ; $a =~ m/${variable}aha/; 和: $a = " ${variable}aha" ; 二者都指向同一字符串:前者在$a中匹配字符串TESTaha.后者把$a设置为字符串 TESTaha。因为正则表达式处理双引号字符串能处理的每个字符,所以可以执行下列操作: $expression = ' hello'; @arrayName = (' elem1', ' elem2'); $variable =~ m/$expression/; # this equals m/hello/; 在这里,我们简单地把$expression扩展为hello而得到m/hello/。这个技巧也可用于数组: $variable =~ m/@arrayName/; # this equals m/elem1 elem2/; 在这里,表达式等价于 m/elem1 elem2/。如果特殊变量$"被设置为 |.则表达式将等价于 m/elem | elem2/,正如我们看到的,它匹配字符串的elem或者elem2。这种方法也可应用于特殊字符: $variable =~ m//x01/27/; # match binary character x01, and # octal character 27. $variable =~ s//t/t/t//; # substitute three tabs for three spaces. 实际上,这里所讨论的除极少数例外以外,Perl处理在m//中的过程的确像处理在双引号中的一样。但是有例外:有某些对正则表达式引擎有明确意义的字符。那么,如果想匹配类似于正斜杠(/)或者园括引(())这样的字符会发生什么呢?这些字符对正则表达式引取有特殊意义:因而不能使用如下语句: $variable=~ m//usr/local/bin/; # matches /usr/local/bin? NO! SYNTAX ERROR 因为Perl将把/解释为正则表达式的结束标记。这里有三种方法去匹配类似于上述特殊字符的方法。第一种方法是利用反料杠来“转义”想匹配的任意特殊字符一包括反斜杠。因而刚才给出的例子可变为: $path =~ m///usr//local//bin/; 该程序尽力匹配 $path中的/usr/local/bin。第二种方法是使用一个不同的正则表达式字符。如果有许多字符要匹配,那么使用反斜杠则会变得很难看(路径字符尤其不好)。 幸运的是,Perl以一种合成形式来确决这个问题。因为在当读者输入m//或s///时需要给每个/加反斜杠,所以正则表达式允许读者将正则表达式的定界符(/)改为自己喜欢的任意字符。例如,我们可以使用双引号(")来避免大量的反斜杠: $variable =~ m"/usr/local/bin"; # Note the quotation marks. $variable =~ m"/"help/""; # If you are going to match quotation # marks, you need to backslash them here. (as per/") $variable =~ S" $variable" $variable"; # works in s///too. 出于好的初衷,我们在本书的前几章使用了这一约定。如果使用"作为读者的正则表达式字符,那么在用起来时它充当了好的记忆法,记住在这里所处理的实际上是字符串的变相反插入;否则,引号就远不如斜杠常用。 Perl允许使用{}()[]来书写正则表达式: $variable =~ m{this works well with vi or emacs because the parens bounce}; $variable =~ m(this also works well); $variable =~ s(substitute pattern) {for this pattern}sg; 这一原则对我们处理多行正则表达式非常方便。因为在这里可以不用圆括号,所以读者可以开始把表达式作为“微型函数”对待(如果读者有像emacs或vi这样的合理的智能编辑器),换句话讲,读者可在表达式的开始和结尾部分之间往返。 第三种方法是利用函数quotemeta()来自动地加反斜杠。如果输入如下代码: $variable =~ m" $scalar"; 则$scalar将为被插且被转变为标量的值。这里有一个警告:任何一个特殊字符都将被正则表达式引擎影响,并且可能引起语法错误。因此,如果标量为: $scalar = " ({"; 那么输入如下代码: $variabie =~ m" $scalar" ; 就等价于是说:$variable =~ m"({",而这是一个运行时语法错误。如果表达式为如下形式: $scalar = quotemeta(' ({'); 则表达式会使$scalar变为/(/{,且把$scalar替换为: $variable =~ m" /(/{" ; 这样才可以匹配到读者愿意匹配的字符串({。 9.3.5 原则5 正则表达式在求值的过程中产生两种情况:结果状态和反向引用。 每次对正则表达式求值时会得到: .指示正则表达式匹配字符串的次数(结果状态)。 .如果希望保存部分匹配,则有—系列称为反向引用的变量。 接下来让我们依次学习他们: 1.结果状态 结果状态表示正则表达式匹配字符的次数。得到结果状念的方法是在标量环境下求正则表达式的值。以下所有例子使用了这—结果变量。 $pattern = ' simple always simple'; $result = ($pattern =~ m"simple"); 这里,result为1,因为模式simple位于simple always simPle中。同样的,给定simple always simple: $result = ($pattern =~ m" complex"); 将使result为空,因为complex不是simple always simple的子字符串,接着: $result = ($pattern =~ s" simple" complex"); 使result为1,因为把simple替换为complex成功了。更进一步: $pattern = ' simple simple'; $result = ($pattern =~ s" simple" complex" g); 情况变得更复杂。在这里,$result为2,因为在simple always simple中simple出现两次,同时正则表达式的g修饰符被使用,这意味着“匹配尽可能多的次数”。(要更详细材料参看本章后面的修饰符)。同样地: $pattern = ' simple still'; if ($pattern =~ m" simple") { print " MATCHED!/n"; } 在if子句中使用$pattern =~ m" simple",而该子句基本上告诉Perl,如果模式$pattern包含有子串simple则打印Matched! 。 2.反向引用 反向引用有点复杂。假定想保存一些匹配供后用,那么为达到该目的,Perl有一个运算符(圆括号()),该运算符可用于包围读者希望匹配的一系列给定的字符。 在正则表达式中用圆括号括住某模式就是告诉解释器“嗨,我希望保存那个数据。” Perl解释器再应请求,且将查找到的匹配保存在一系列特珠的变量中($1,$2,$3…$65536),这些变量可用来查询第一个、第二个、第三个等等圆括号匹配,这些变量于是可以通过查看相应的变量或在数组环境下对正则表达式进行求值而且进行访问。例如: $text = " this matches ' THIS' not 'THAT' "; $text =~ m" ('TH..' )"; print " $1/n"; 在这里,字柳HIS被打印出来 —— Perl已经将它们保存在$1中,以后再打印$1。 然而,该例子揭示了更多内容,例如: 1)通配符(字符点(.)匹配任意字符)。如果THIS不在字符串中,模式(TH..)将欣然匹配 THAT。 2)正则表达式匹配一行上出现的第一处模式。THIS因为首先出现,所以被匹配。同时,按缺省regexp行为,THIS将总是被匹配的第一个字符串。(可以用修饰符改变缺省值,详细情况稍后介绍)。 图 9-3 表示了这一匹配过程如何进行。 在图 9-3 中每个圆括号与自已的数字变量一道运行。 这里有更多的例子: $text = ' This is an example of backreferences'; ($example, $backreferences) = ($text =~ m" (example).*(backreferences)" ); 这里又用了通配符来分开两个文本字符串$example和$backreferences。这些字符串存放在$1和$2中,且随后立即赋给$example和$backrefercences。该过程在图9—4中说明。 然而应注意的是仅当文本字符串匹配时,给$example和$bacbreference赋值的过程才发生。当文本字符串不匹配时,$example和 backreferences是空的。这里有更好的同样的例子,该例子包含在if语句中,仅在匹配时打印$1和$2。 if ($text =~ m" (example).*(back)") { print $1; # prints ' example' -- since the first parens match the text example. print $2; # prints ' back' -- since the second parens match the text back } 这样,如果正则表达式根本不匹配将发生什么?如果使用下面的模式: $text = ' This is an example of backreferences'; $text =~ s" (examplar).*(back)" doesn't work"; print $1; $1因正则表达式匹配不成功而不能被赋值。更重要地,Perl不会告诉读者它没有给$1赋任 何值。最后一例展示了关于正则表达式的两点重耍内容: 1)正则表达式是“要么全部要么什么也没有”的处理,只因为back字符串在模式内才能匹 配,所以: This is an example of backreferences' 并不意味着整个表达式达到匹配。因为exemplar不在字符串中,因而替换失败。 2)如果正则表达式失败,反向引用不能得到赋值。因此,不能肯定将打印出什么内容。当跟踪逻辑问题时,这就是让人吃惊的原因;且经常是Perl gotcha。$1只是一个正则变量,并且(与Perl语法相反)如果正则表达式失败则反向引用不被设置为“空白”。有人认为这是一个缺陷,然而另有人认为这是一个特色。不过,当分析下面的代码时第二点变得非常明显。 1 $a = ' bedbugs bite'; 2 $a =~ m" (bedbug)"; # sets $1 to be bedbug. 3 4 $b = ' this is nasty'; 5 $b =~ m" (nasti)"; # does NOT set $1 (nasti is not in ' this is nasty' ). 6 # BUT $1 is still set to bedbug! 7 print $1; # prints ' bedbug'. 在这种情况下,$1为字符串bedbug,因为第5行的匹配失败!如果希望得到nasti,好吧,那是自己的问题。这种Perl化的行为可能让人不知所措。考虑的是自己要当心。 3.使用反向引用的一般构造法 如果想避免这种很平常的缺陷(读者想得到一个匹配,但是没有得到并且用前面的匹配为 替代而结束),在把反向引用赋给变量时只要应用下列三个构造法之一: 1)短路方法。核查匹配,如果匹配发生,此时且只有此时用' &&'进行赋值,例如: ($scalarName =~ m" (nasti)" ) {$matched = $1;} 2)if子句。将匹配放于if子句中,如果if子句为真,此时且只有此时才为模式赋值。 if ($scalarName =~ m" (nasti)" ) {$matched = $1;} else {print "$scalarName didn't match"; } 3)直接赋值。因为可以直接把正则表达式赋给一个数值,所以可始终利用这一点。 ($match1, $match2) = ($scalarName =~ m" (regexp1).*(regexp2)" ); 读者的所有模式的匹配代码看起来应该与前述三个例子中的一个相似。缺少这些形式,那么就是在没有安全保证的条件下进行编码。如果读者从不想有这种类型的错误的话,那么这些形式将节省读者的大量时间。 4.在正则表达式中使用反向引用 当希望使用s" " "运算符或者用m" "运算符对一些复杂模式进行匹配很困难时,Perl提供了读者应意识到的有用功能。这个功能就是反向引用可以用于正则表达式自身。换句话说,如果用圆括号括住一组字符,那么就可以在正则表达式结束之前使用反向引用。如果想在s" " "的第二部分(带下划线)中使用反向引用,那么要使用语法$1,$2等。如果想在m" "或者s" " "的第一部分(带下划线)使用反向引用,那么使用语法/1/2等。下面是一些例子: $string = ' far out'; $string =~ s "(far)(out)" $2 $1"; # This makes string ' out far'. 我们在该例中只是将单词far out转换为out far。 $string = ' sample examples'; if ($string =~ m" (amp..) ex/1") {print " MATCHES!/n"; } 这个例子有点复杂。第—个模式(amp..)匹配字符串ample。这意味转整个模式成为字符串ample example,其中带下划线的文本对应于/1。因此,模式匹配的是 sample examples。 下面是同样风格更复杂的例子; $string = ' bballball'; $string =~ s" (b)/1(a...)/1/2" $1$2"; 让我们详细地看看这个例子。该例完成匹配,但是原因不是太明显。对这个字符串的匹配有五个步骤: 1)在圆括号中的第一个b匹配字符串的开头,接着将其存放在/1和$1中。 2)/1于是匹配字符串中的第二个b,因为与b相等,而第二个字符碰巧是b。 3)(a..)匹配字符串all且被存在/2和$2中。 4)/1匹配下一个b。 5)因为/2等于all所以匹配下一个且是最后三个字符(all)。 将他们放到一起就得到正则表达式匹配bballball,或者说是整个字符串。既然$1等于' b',$2等于all,则整个表达式: $string = ' bballball' ; $string =~ s" (b)/1(a..)/1/2" $1$2"; (在这个例子中)转换为如下代码: $string =~ s" (b)b(all)ball" ball"; 或者用行话讲,用bballball替换ball。 ’ 正则表达式看起来很像图 9-5 所示。 s" " "中有一些复杂的反向引用。如果理解了最后一个例子。那么读者在理解Perl的正则表达式如何工作方面远远走在了前面。反向引用可能而且确实会变得更糟。 5.嵌套反向引用 嵌套反向引用对于复杂的难以用单一顺序(一个字符串跟在另一个字符串后面)进行匹配的字符串作用明显。例如下面的表达式: m" ((aaa)*)" ; 使用 * 来匹配多次出现的aaa:即匹配",aaa,aaaaaa,aaaaaaaaa。换句话说,Perl匹配一行中有多个3a的模式。但是这个模式不会匹配aa。假定想匹配如下的字符串: $string = ' softly slowly surely subtly'; 那么使用嵌套园括号后下面的正则表达式会匹配: $string = m" ((s...ly/s*)*)" ; # note nested parens. 在该例中,最外层的圆括号捕获全部字符串:softly slowly surely subtly 。最内层的圆括号捕获字符串的组合,这种组合是以s开头,以ly结尾且ly后跟空格形成的。因此,正则表达式先捕获surely,将其抛开,然后捕获 slowly,将其抛开,然后捕获surely,最后捕获subtly。这里有一个问题,反向引用按什么顺序出现?读者可能在这个问题上很容易迷惑。是外层圆括号先出现呢,还是内层的内括圆号先出现?最简单的解决办法是记住以下三条原则: 1)在表达式中,一个反向引用越在前,它对应的反向引用编号就越小。例如: $var =~ m" (a)(b)"; 该例中,反向引用(a)变为$1,(b)变成了$2。 2)一个反向引用如果它包含范围越广,则它的反向引用编号就越小。例如: $var =~ m" (c(a(b)*)*)"; 该例中,包含全部内容(m "(c(a(b)*)*)" )的反向引用成为$1。有a嵌套在里面的表达式 m"(c(a(b)*)*)"成为$2。在(m"(c(a(b)*)*)" )中带有b的嵌套表达式成为$3。 3)在两个规则冲突的情况下,规则1优先。在语句$var =~ m" (a)(b(c))" 中,(a)成为$1,b(c)成为$2,(c)成为$3。 因而,在这个例子中,(s...ly/s*)*成为$1,(s...ly/s*)*成为$2。 注意这里有另一个问题。让我们一起返回到刚开始的复杂的正则表达式: $string = 'softly slowly surely subtly' $string = m"(((s...ly/s*)*)"; # note nested parens. 这里(s...ly/s*)*匹配什么呢?它匹配多个字符串;首先是softly,接着是slowly,再接着是surely,最后是subtly。既然(s...ly/s*)*匹配多个字符串,那么Perl会抛弃掉第一个匹配而使$2成为subtly。 即便有这些规则,嵌套圆括号仍然可能引起混乱。要做的最好事情就是实践。再一次用这些逻辑的不同组合去实现正则表达式,然后把它们交给Perl解释器。这样做可让读者明白反向引用是以什么次序被Perl解释器进行解释的。 |