了解正则表达式操作符的优先级

转自:http://book.51cto.com/art/201104/259073.htm

第3章 正则表达式

Perl的正则表达式本身就相当于一门语言了,而且这门语言甚至比Perl更复杂。

我们不会用到正则表达式的全部特性,但其中有些特性的确能大大简化日常工作。本章介绍其中较为常用的一部分特性。

尽管为了提升效率,开发人员已经对正则表达式引擎内部作了大量优化,但即使经验丰富的开发人员,也偶尔会写出效率极为低下的匹配和替换表达式。

效率并不总是我们的首要目标。事实上,在软件开发过程中,效率不应该是我们的首要目标。通常来说,程序员的首要任务是提供完备、稳定而正确的解决方案。当然在开发时,时刻注意效率问题总还是没有坏处的。

由于现在Perl已经能够处理Unicode字符,所以Perl的正则表达式同样能够处理字节(byte)、字符(character)和字素(grapheme)。不仅如此,正则表达式还可以处理字符属性(character property)。我们把大部分此类问题保留到第8章再详细讨论,也就是说,第8章也将包含不少与正则表达式相关的内容。

条款28 了解正则表达式操作符的优先级(1)

"正则表达式"一词中之所以包含"表达式",是因为构成和解析正则表达式的语法近似于算术表达式。诚然,正则表达式与算术表达式的作用各不相同,但理解二者的相似性,有助于写出更严谨的正则表达式,或者说更完美的Perl程序。

正则表达式由原子和操作符组成。原子(atom)是构成正则表达式的基本单位,通常是指仅匹配单个字符的匹配模式。例如:

 
 
  1. a           # 匹配字母a  
  2. \$          # 匹配字符$  
  3. \n          # 匹配换行符  
  4. [a-z]   # 匹配任何一个小写字母  
  5. .           # 匹配除\n以外的任意字符  
  6. \1          # 反向引用所匹配到的第一组捕获内容(文本长度不限) 

此外还有一些特殊的"零宽度"原子 ,例如:

 
 
  1. \b          # 单词边界,从\w转换到\W的分界点,反之亦然  
  2. ^           # 匹配字串行首位置  
  3. \A          # 字串的绝对行首  
  4. \Z          # 字串末尾位置或换行符位置  
  5.             # 可能是也可能不是零宽度  
  6. \z          # 绝对行尾位置,之后再无其他内容 

原子由正则表达式操作符修饰或联结在一起。与算术表达式相似,正则表达式的操作符之间也是有优先级次序的。

1. 正则表达式的优先级

幸运的是,正则表达式只有四层优先级。试想一下如果正则表达式的优先级和数学表达式一样多一样复杂,会是怎样一种情况!

圆括号和其他分组操作符拥有最高优先级。表3-1列出了正则表达式操作符的优先级。

量词与它所修饰的元素的结合最为紧密,不论所修饰的是原子还是分组:

 
 
  1. ab*c                # 匹配ac、abc、abbc、abbbc等  
  2. abc*                # 匹配ab、abc、abcc、abccc等  
  3. ab(c)*          # 同上,并捕获字母c  
  4. ab(?:c)*        # 同上,但不捕获字母c  
  5. abc{2,4}        # 匹配abcc、abccc、abcccc  
  6. (abc)*          # 匹配空字串、abc、abcabc等 

表3-1 正则表达式操作符优先级次序(从高到低)

优先次序

操 作 符

描述

最高级

() (?:),等等

圆括号或其他分组操作符

 

? + * {m,n} +? ++,等等

重复次数

 

^ $ abc \G \b \B [abc]

字符序列、文字字符、

字符组、断言

最低级

a|b

多选结构

两个原子顺次排列称之为序列(sequence)。虽然没用标点符号,但序列也是一种操作符。为清晰起见,下文使用圆点(o)表示序列关系。上述例子因此变为:

 
 
  1. aob*oc          # 匹配ac、abc、abbc、abbbc等  
  2. aoboc*          # 匹配ab、abc、abcc、abccc等  
  3. aobo(c)*        # 同上,同时捕获字母c  
  4. aobo(?:c)*  # 同上,但不捕获字母c  
  5. aoboc{2,4}  # 匹配 abcc、abccc、abcccc  
  6. (aoboc)*        # 匹配空字串、abc、abcabc等 

现在操作符之间的优先级关系是不是明显许多了?

在优先级次序中级别最低的,要数多选结构了。下面继续使用o记号提示:

 
 
  1. eod|joo         # 匹配ed或jo  
  2. (eod)|(joo)     # 同上  
  3. eo(d|j)oo       # 匹配edo或ejo  
  4. eod|joo{1,3}    # 匹配ed、jo、joo、jooo  
  5.  
  6. 像^或\b这样的零宽度原子,与其他原子的优先次序是一致的:  
  7. ^eod|joo$       # 匹配行首的ed,或行尾的jo  
  8. ^(eod|joo)$     # 匹配仅含ed或仅含jo的行 

优先级次序不太好记。剔除多余括号属于高级技巧,在正则表达式中尤其如此,因而特别需要留意,以防删除过多而误事:

 
 
  1. # 这封邮件是谁发给我的?  
  2. /^Sender|From:\s+(.*)/; # 错误!它可以匹配像这样的伪造头:  
  3.                         # X-Not-Really-From: faker 

上述模式的本意是要匹配邮件头中Sender:或From:开头的行,但实际上能匹配到的内容显然不是我们所期望的。如果加上合适的括号,意思就清晰了:

 
 
  1. /(^Sender)|(From:\s+(.*))/; 

添加一对圆括号,或非捕获型括号(?:)(见条款32),问题就能迎刃而解:

 
 
  1. # 改良版本  
  2. /^(Sender|From):\s+(.*)/;       # 变量$1保存的是单词Sender或From  
  3. /^(?:Sender|From):\s+(.*)/; # 变量$1保存的是真正要提取的内容 

2. 双引号变量内插

Perl正则表达式的变量插值方式与双引号字串内的变量插值方式相同。变量名称以及\U和\Q这样的字符转义,并不属于正则表达式原子,因而正则表达式解析器不会处理它们。内插是一个单独的过程,发生在Perl解析正则表达式之前:

 
 
  1. /te(st)/;       # 测试系统变量$_是否匹配test  
  2. /\Ute(st)/;     # 匹配TEST  
  3. /\Qte(st)/;     # 匹配te(st)  
  4. $x = 'test';  
  5. /$x*/;              # 匹配tes、test、testt等  
  6. /test*/;            # 同上 


不明白变量插值和正则表达式解析过程的先后顺序,常常会导致混淆误用的情况。请看下面的例子,要是把插值变量当成了正则表达式的原子,会发生什么情况:

 
 
  1. # 读入一个模式,匹配该模式出现两次的情形  
  2. chop( $pat = <STDIN> ); # 例如,读入的模式为bob  
  3. print "matched\n" if /$pat{2}/; # 错误,实际相当于/bob{2}/  
  4. print "matched\n" if /($pat){2}/;   # 正确,实际相当于/(bob){2}/  
  5. print "matched\n" if /$pat$pat/;    # 简单罗列式,虽然相等,倒也正确 

上例中,如果用户输入bob,第一条正则表达式所匹配的就是bobb,因为在解析正则表达式之前,变量$pat已经被实际内容替换了。

上面三条正则表达式都隐藏着另一个陷阱。假如用户输入的是字串hello :-),就会导致严重的运行时错误。变量内插后得到的实际正则表达式会从/($pat){2}/变为/(hello :-)){2}/,不光是毫无意义,连括号也不对称了。Perl会报告正则表达式错在哪里:

 
 
  1. Unmatched ) in regex; marked by <-- HERE in  
  2.   m/(hello :-)) <-- HERE {2}/ 

这种问题我们可以用qr//解决(见条款40)。

对于括号、星号、点号之类的特殊字符,如果不想把它们作为正则表达式元字符使用,可以借助quotemeta操作符,或是转义操作符\Q。quotemeta和\Q会在任何不是字母、数字及下划线的字符之前加上反斜线作转义处理。

下面的代码从标准输入读取字串,使用quotemeta将字符中的特殊字符转义,然后再用于匹配。如果输入仍旧是hello :-),那么转义后会变为hello\ \:\-\),可以放心地用这个正则表达式进行匹配了。

 
 
  1. chomp( $pat = <STDIN> );  
  2. my $quoted = quotemeta $pat;  
  3. print "matched\n" if /($quoted){2}/; 

或者,直接在表达式中使用转义操作符\Q和\E :

 
 
  1. chomp( $pat = <STDIN> );  
  2. print "matched\n" if /(\Q$pat\E){2}/; 

跟使用正则表达式的其他语法结构一样,一点点疏忽都可能造成正则表达式崩溃:

 
 
  1. # 它实际上相当于/hello \ \:\-\)\{2\}/,显然这是一个致命错误  
  2. print "matched\n" if /(\Q$pat){2}/; # 错误!漏掉了\E 

3. 要点

要留意正则表达式的优先级。

可以使用圆括号将正则表达式分组。

使用\Q或quotemeta将元字符转义为普通字符。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值