`
足至迹留
  • 浏览: 485964 次
  • 性别: Icon_minigender_1
  • 来自: OnePiece
社区版块
存档分类
最新评论

<基础-3> 正则的特性和流派

阅读更多
正则表达式的特性和流派
经过前面的介绍,我们大概见识了正则表达式的样子,也见识了若干使用正则表达式的工具软件,工具不同正则表达式的写法和用法都有很大的不同。
在特定的宿主语言或工具软件中使用正则表达式时,主要有3个问题值得注意
1.支持的元字符,以及这些元字符的意义。这通常称为正则表达式的“流派(flavor)”
2.正则表达式与语言或工具的交互方式。譬如如何进行正则表达式操作,容许进行哪些操作,以及这些操作的目标文本类型。
3.正则表达式引擎如何将表达式应用到文本。语言或工具的设计者实现正则表达式的方法,对正则表达式能够取得的结果有重要的影响。

关于正则表达式的起源和发展历程,感兴趣的大家可以查看《精通正则表达式》的第三章。

3.1正则表达式的注意事项和处理方式
在使用正则表达式之前首先要清楚使用的工具支持的元字符,然后就是正则表达式的句法规则,它告诉应用程序如何去做。egrep是一个简单的例子,因为正则表达式是作为命令行参数传过去的。复杂系统,例如程序设计语言中的正则表达式,需要更多的包装,系统才能知道哪些部分是正则表达式,需要如何处理。
下一步是考察我们能够对匹配结果进行的操作。其中最基本的是匹配(检查一个正则表达式是否能匹配一个字符串,或从字符串中提取信息),以及查找和替换,根据匹配的结果修改字符串。

一般来说,程序设计语言有3种处理正则表达式的方式:集成式(integrated),程序式(procedural)和面向对象式(object-oriented)。第一种方式中,正则表达式直接内建在语言之中,perl,awk就是这样。其他两种方式中,正则表达式不属于语言的低级语法。大多数语言采用的都是这两种方式之一,包括java,.NET,TCL,python,ruby等。

3.2字符串,字符编码和匹配模式
在深入讲解常见的各类元字符之前,还需要了解一些重要的问题:作为正则表达式的字符串,字符编码和匹配模式
这些概念并不复杂,在理论和实践中都是如此,不过,对其中的大多数来说,因为各种实现方式之间存在细小差异,我们很难预先知道他们准确的实际使用方式。

3.2.1 作为正则表达式的字符串
perl,awk,sed之外的大多数语言来说,正则引擎接收的是以普通字符串形式提供的正则表达式,这些字符串类似”^From: (.*)”。对程序员来说,在构造作为正则表达式的字符串时,还需要留意编程语言定义的字符串元字符。如java反斜线”\”要转义”\\”才行,单词分界符”\b”要写成\\b才行。
每种语言的字符串文字都规定了自己的元字符,有些语言甚至包含了多种字符串文字,所以不存在普适性的规则。

3.2.2 字符编码
字符编码是一种写明的共识,它规定不同数值的字节应该如何解释。在ASXII编码中,值为十进制的110的字节代表字符’n’,不过在EBCDIC编码中代表’>’。 字节的值是一样的,但解释不一样,因为这是由不同的人规定的,没有明确的标准判断各种编码的优劣。

对我们来说,重要的问题在于,如果我们期望使用某种特定编码的数据,程序是否会这样做。例如,我们使用Latin-1编码值为234,116,101和115的4个字节表示法语单词”Ê tes”,我们期望正则表达式”^\w+$”或”^\b”来匹配,如果程序中的\w或\b能支持Latin-1字符,就可以正常工作,否则不行。这取决于语言中正则表达式对编码的支持程度

3.3 正则模式和匹配模式
许多正则引擎都支持多种不同的模式,他们规定了正则表达式应该如何解释和应用。比如perl的”/i”修饰符不区分大小写匹配的模式。
许多流派中,模式可以完全作用于整个表达式,也可以单独作用于某个子表达式。整体应用时通过修饰符或者选项来决定的,如perl的/i或java.util.regex的Pattern.CASE_INSENSITIVE标志。如果语言支持的话,应用到目标字符串中部分文本的模式是通过一个正则结构来实现的。如”(?i)”来开启不区分大小写的模式,”(?-i)”来停用该匹配。

注意,不同的语言,对模式使用的语法是不一样的。
1.不区分大小写匹配模式
此模式很常见,它在匹配过程中会忽略字母的大小写。此功能也必须依赖于正确的字符编码支持。

2.宽松排列和注释模式
此模式会忽略字符组外部的所有空白字符。字符组内部的空白字符仍然有效(java.util.regex是例外)。#符号和换行符之间的内容视为注释。
在java.util.regex中,字符组之外的所有空白字符并非都会被忽略,而是作为一个“无意义元字符”。在理解”\12 3”时,这种区分很重要,因为它表示”3” 接在“\12”后面,而不是忽略空格”\123”。
当然,空白字符的定义取决于所采用的字符编码的定义,以及此编码对空白字符的支持程度。大多数程序只能识别ASCII的空白字符。

3.点号通配符(也叫单行模式)
前面我们说过正则的元字符点号(.)能代表所有字符,但通常点号是不能匹配换行符的。这是匹配模式的差别。通常,能够处理多行文本的工具(如文本编辑器)通常不容许点号匹配换行符。
对现代编程语言来说,点号能够匹配换行符的模式和不能匹配的模式同样有用。这两种模式哪个更方便,取决于具体的情况。许多程序提供了两种方法供正则表达式选择。

4.不幸的命名
/s修饰符对应的匹配模式第一次出现在perl时被称为单行文本模式。其实单行文本模式指的是点号不受限制,可以匹配任何字符。

5.增强的行锚点模式(也叫多行文本模式)
多行文本模式会影响到行锚点”^”和”$”的匹配。通常情况下”^”不能匹配字符串内部的换行符,而只能匹配目标字符串的起始位置。但是在多行文本模式下,它能匹配字符串中内嵌的文本行的开头位置。
“$”也是这样,在多行文本模式下可以匹配字符串内部的换行符。

6.文字文本模式
“文字文本(literal text)”模式几乎不能识别任何正则表达式元字符。例如,文字文本模式下”[a-z]*”匹配字符串”[a-z]*”,而不是搜索这个正则表达式。支持正则表达式的程序通常也提供了普通的字符串搜索功能。

3.4 常用的元字符和特性
下面介绍下常见正则表达式元字符和概念。这里的介绍并不是全面彻底的,不过也没有任何一种正则工具涉及其中的所有内容。使用时最好参考使用的工具提供的使用手册

3.4.1 字符表示法
这一组元字符能够以清晰美观的方式匹配其他方式中很难描述的某些字符。
1.字符缩略表示法
许多工具软件提供了某些控制字符的元字符,其中有一些在所有机器上都是不变的,但也有些很难输入或观察。


在许多工具中,\n和\r的意义是随操作系统的变化而变化的,所以在使用时应该格外小心。如果需要在程序可能运行的所有平台上都能通用的换行符,请使用\n

3.4.2 字符组及相关结构
在许多流派中,都有多种方法在正则表达式的某个位置指定一组字符,不过最通行的方法还是使用普通字符组。

1.普通字符组[a-z]和[^a-z]
我们已经介绍过字符组的基本概念,不过还要强调,元字符的规定在字符组内外是有差别的。如,在字符组内部”*”永远不是元字符,而”-”通常都是元字符,但如果在字符组内是第一个字符就不是元字符。
在大多数系统中,字符组内部的顺序是无关紧要的,而且使用范围表示法而不是列出范围内的所有字符并不会影响执行速度(例如,[0-9]与[0123456789]是一样的)。相反,某些实现方式不能完全优化字符组(比如sun提供的java regex package),所以最好是使用范围表示法,因为如果有差别,这种表示法速度会快一些。

字符组通常表示肯定断言。也就是说,他们必须匹配一个字符。排除型字符组仍然需要匹配一个字符,只是他没有在字符组中列出而已。把排除型字符组理解为“匹配一个未列出的字符的字符组”或许更容易理解一些。

2.几乎能匹配任何字符的元字符:点号
在某些工具软件中,点号用来缩略表示可以匹配任何字符的字符组,而在其他工具中,点号能匹配除了换行符之外的任何字符。
1)在sun的java regex package之类的支持unicode的系统中,点号不能匹配unicode的终结符。
2)匹配模式(单行模式还是多行模式)会改变点号的匹配规则。
3)posix规定,点号不能匹配NUL(值为0的字符),尽管大多数脚本语言容许文本中出现NULL(而且可以用点号来匹配)。


3.点号,还是排除型字符组
如果所使用的工具能够在多行文本中搜索,请务必注意点号,它在通常情况下不能匹配换行符,而排除型字符组”[^"]”通常可以。

4.字符组简记法: \w,\d,\s,\W,\D,\S



5. 完整的字符组集合运算:[[a-z] && [^aeiou]]
sun的java regex package 中的字符组能够进行完整的集合运算(交集,并集,差集)。
并集:[abcxyz]也可以表示为[[abc][xyz]],或[abc[xyz]]或[[abc]xyz]
交集:[this && [^that]]表示[this]减去[that]。

3.4.3锚点和其他“零长度断言”
锚点(^,$)和其他“零长度断言”并不会匹配实际的文本,而是寻找文本的位置。
1.行/字符串的起始位置:^, \A
脱字符”^”匹配需要搜索的文本的起始位置,如果使用了增强的行锚点匹配模式(多行模式),它还能匹配每个换行符之后的位置。在某些系统中,增强模式下”^”还能匹配Unicode的行终结符。
如果可以使用,则无论在什么匹配模式下,”\A”总是能够匹配待搜索文本的起始位置。

2.行/字符串结束位置:$, \Z和\z
“行结束位置”的概念比行开头位置更复杂。在不同的工具软件中,$的意义也不同,不过最常见的意思是匹配字符串的末尾。也可以匹配整个字符串末尾的换行符之前的位置。比如”s$”匹配s结尾的行,有时也可以匹配以s和换行符结尾的行。
$的另两种常见的意思是,只匹配目标文本的结束位置,或是匹配任何一个换行符之前的位置。在某些Unicode系统中,这些规则中的换行符会被替换为Unicode的行终结符(java为了处理Unicode中的行终结符,为$设定了非常复杂的语义)。
匹配模式可以改变”$”的意义,匹配字符串中的任何换行符(或者是Unicode的行终结符)。

如果支持,”\Z”通常表示”未指定任何模式下”$匹配的字符,通常是字符串的末尾位置,或者是在字符串末尾的换行符之前的位置。作为补充,”\z”只匹配字符串的末尾,而不考虑任何换行符。

3.匹配的起始位置(或者是上一次匹配的结束位置):\G
“\G”首先出现在perl中,在使用/g的匹配中,\G对地带操作非常有用。它能匹配上一次匹配结束的位置。在一次迭代时,”\G”匹配字符串的开头,和\A一样。
如果匹配不成功,”\G”的匹配会重新指向字符串的起始位置。这样,如果重复应用某个正则表达式,例如进行perl的”s/…/…/g”操作,在匹配失败的同时,”\G”也会指向字符串的开头位置,这样以后进行其他类型的匹配操作便不受影响。关于\G更详细的用法,请参考对应语言的说明。


4.单词分界符:\b,\B,\<,\>…
单词分界符的作用与行锚点一样,也是匹配字符串中的某些位置。单词分界符可以分为两类,一类中单词起始位置分界符和结束位置分界符是不相同的(通常是\<和\>),另一类则以统一的分界符来匹配(通常是\b)。两类都提供了非单词分界符(通常是\B)。
单词分界符通常可以这样理解,这个位置的以便是“单词字符”,另一边不是。


5.顺序环视(?=…),(?!...)和逆序环视(?<=…), (?<!...)
在前面“使用环视功能在数值中插入逗号”的例子中,我们介绍过顺序环视和逆序环视结构(统称环视)。但关于他们还有很重要的一点没有讲,那就是环视结构中能够出现什么样的表达式。大多数实现方式都限制了逆序环视中的表达式的长度(但是顺序环视则没有限制)。
perl和python的限制是最严格的,逆序环视只能匹配固定长度的文本。使用(?<!\w)和(?<!this|that)不会出错,但是(?<!books?)和(?<!^\w+:)则不行,因为他们匹配的文本的长度是不确定的。


3.5 注释和模式修饰符
在许多流派中,使用下面的结构,就能够在正则表达式内部,切换使用之前介绍的正则表达式模式和匹配模式。

3.5.1 模式修饰符: (?modifier),例如(?i)和(?-i)
现在许多流派容许正则表达式中设定匹配模式。最常见的就是(?i),它会启用不区分大小写的匹配,而(?-i)会停用此功能。例如”<B>(?i)very(?-i)</B>”会对中间的very进行不区分大小写的匹配。而两端的tag仍然必须大写。
这个例子在大多数系统中可以运行,例如perl,PHP,java.util.regex,ruby和.net可以。在python和tcl中不行,他们不支持。
模式修饰符中能够出现的不只有i。大多数系统中,我们至少可以使用下面列出的修饰符,有些系统还提供了更多的选项。


3.5.2 模式作用范围: (?modifier:…),例如(?i:…)
如果所使用的系统支持模式修饰范围,这样使用起来就更加简化。”(?i:…)”表示模式修饰符的作用范围只有在括号内有效。这样”<B>(?i)very(?-i)</B>”就可以简化为”<B>(?i:very)</B>”。


3.5.3 注释: (?#...)和#...
某些流派支持用”(?#...)”添加注释。实际上,如果流派支持宽松排列和注释模式,就很少使用这种功能。不过,如果在字符串文字中很难插入换行符,用这种格式加入注释就非常方便。

3.5.4 文字文本模式: \Q…\E
\Q…\E是由perl引入的,它会消除其中除\E之外所有元字符的特殊含义(如果没有\E,就会一直作用到正则表达式末端)。其中的所有字符都会被当成普通文字文本来对待。如果在构建正则表达式时包含变量,此功能就非常有用。
目前支持”\Q…\E”的引擎只有java.util.regex和pcre(包括PHP的preg套件)。在低于1.6的java中,对这个模式的支持是不可靠的,不建议使用。

3.6 分组,捕获,条件判断和控制
3.6.1 捕获/分组括号: (…)和\1,\2,…
普通的无特殊意义的括号通常有两种功能:分组和捕获。普通括号常见的形式是”(…)”,但有的流派中使用”\(…\)”,例如sed,vi和grep.
捕获型括号的编号是按照开括号出现的次序,从左到右计算的。如果提供了反向引用,则这些括号内的子表达式匹配的文本可以在表达式后面部分用\1,\2来引用。
括号的常用功能之一是从字符串中提取数据。括号中的子表达式匹配的文本在不同的程序中可以通过不同的方式引用。如perl用$1,$2引用。

3.6.2 仅用于分组的括号: (?:…)
仅用于分组的括号不能用来提取文本,而只能用来规定多选结构或量词的作用对象。他们不会按照$1,$2之类编号。匹配的性能要比捕获型括号高。仅用于分组的括号也称为非捕获型括号。

3.6.3 固化分组: (?>…)
如果详细了解正则引擎的匹配原理(后面会介绍),就很容易理解固化分组。就是一旦括号内的子表达式匹配之后,匹配的内容就固定下来,在接下来的匹配过程中也不会变化,除非整个固化分组的括号都被弃用,在外部回溯中重新应用。

比如,”i.*!”能够匹配”iHola!”,但是如果”.*”在固化分组中”i(?>.*)!”就无法匹配。因为”.*”会尽可能多的匹配内容,直到匹配的最后的”!”,如果固化分组了,它匹配后不会交还”!”,导致正则表达式最后的”!”无法匹配。

3.6.4 多选结构: …|…|…
多选结构能在同一个位置测试多个子表达式。每个子表达式称为一个多选分支(alternative)。多选结构的优先级很低,所以”this and|or that”的匹配等价于”(this and|(or that))”,而不是”this (and|or) that”

3.6.5 条件判断: (?if then | else)
这个结构允许用户在正则表达式中使用if/then/else判断。如果if部分为真,则尝试then的表达式,否则尝试else部分(else部分可以不出现)。
if的种类因流派的不同而不同,但是大多数实现方式都容许在其中引用捕获的子表达式和环视结构。

3.7 量词
3.7.1 匹配优先量词: *,+,?,{min, max}
量词(星号,加号,问号,以及区间元字符)能够限制作用对象的匹配次数。匹配优先就是尽可能多的匹配符合要求的内容。

3.7.2 忽略优先量词: *?,+?,??,{min,max}?
我们发现在匹配优先量词后面都加了个问号,这样就成了忽略优先量词,他们会尽可能少的匹配内容,只需要满足下限就成功。

3.7.3 占有优先量词: *+, ++, ?+, {min,max}+
这些量词就是在匹配优先量词后面加个了加号,目前只有java.util.regex和pcre提供。占有优先量词类似普通的匹配优先量词,不过他们一旦匹配某些内容,就不会”交还”这点有点类似固化分组。后面介绍匹配原理时就很容易理解他的含义了。
  • 大小: 150 KB
  • 大小: 42.3 KB
  • 大小: 127.7 KB
0
1
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics