正则表达式是使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些符合某个模式的文本。当前常见的正则表达式主要有两种派系,一种是perl衍生出来的PCRE(Perl Compatible Regular Expression),另一种是POSIX规范兼容的正则表达式BRE和ERE。本文主要对其进行总结和对比。
一、Perl正则表达式
许多程序设计语言都支持利用正则表达式进行字符串操作。在Perl中就内建了一个功能强大的正则表达式引擎,受到众多程序员的认可,甚至一些其他语言(如JAVA)的正则表达式引擎也参考了Perl。
1)简介
因篇幅限制,本文仅仅讨论正则表达式,不过多介绍Perl的语言特性。作为背景,这里只简要介绍Perl语言里和正则表达式相关的一些语法。
- 在 Perl 程序中,正则表达式有三种存在形式,他们分别是:
匹配:m/<regexp>/ d(还可以简写为 /<regexp>/d )
替换:s/<pattern>/<replacement>/ d
转化:tr/<pattern>/<replacemnt>/d
其中模式匹配分隔符『/』可以用任意其他符号代替,如s|<pattern>|<replacement>|
『d』为可选修饰符号,有i(不区分大小写)、x(添加空格)、g(全局匹配)等
- Perl默认对$_进行匹配,如果要对其他容器进行匹配则需要用到绑定操作符『=~』或『~=』(右边匹配左边)
- 自动匹配变量:『$&』, 『$`』, 『$' 』 ,三个值一起得到原始内容;
匹配上的那部分字串自动存储在『$&』中,前一部分存储在『$`』中,而后一部分存储在『$' 』中。
2)匹配字符
字符 | 描述 |
---|---|
\ | 将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,“n "匹配字符"n "。"\n "匹配一个换行符。串行"\\ "匹配"\ "而"\( "则匹配"( "。 |
^ | 匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n "或"\r "之后的位置。 |
$ | 匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n "或"\r "之前的位置。 |
* | 匹配前面的子表达式零次或多次。例如,zo*能匹配“z "以及"zoo "。*等价于{0,}。 |
+ | 匹配前面的子表达式一次或多次。例如,“zo+ "能匹配"zo "以及"zoo ",但不能匹配"z "。+等价于{1,}。 |
? | 匹配前面的子表达式零次或一次。例如,“do(es)? "可以匹配"does "或"does "中的"do "。?等价于{0,1}。 |
{n} | n是一个非负整数。匹配确定的n次。例如,“o{2} "不能匹配"Bob "中的"o ",但是能匹配"food "中的两个o。 |
{n,} | n是一个非负整数。至少匹配n次。例如,“o{2,} "不能匹配"Bob "中的"o ",但能匹配"foooood "中的所有o。"o{1,} "等价于"o+ "。"o{0,} "则等价于"o* "。 |
{n,m} | m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3} "将匹配"fooooood "中的前三个o。"o{0,1} "等价于"o? "。请注意在逗号和两个数之间不能有空格。 |
? | 当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo ","o+? "将匹配单个"o ",而"o+ "将匹配所有"o "。 |
. | 匹配除“\ n "之外的任何单个字符。要匹配包括"\ n "在内的任何字符,请使用像"(.|\n) "的模式。 |
(pattern) | 匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用“\( "或"\) "。 |
(?:pattern) | 匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符“(|) "来组合一个模式的各个部分是很有用。例如"industr(?:y|ies) "就是一个比"industry|industries "更简略的表达式。 |
(?=pattern) | 正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,“Windows(?=95|98|NT|2000) "能匹配"Windows2000 "中的"Windows ",但不能匹配"Windows3.1 "中的"Windows "。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。 |
(?!pattern) | 正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如“Windows(?!95|98|NT|2000) "能匹配"Windows3.1 "中的"Windows ",但不能匹配"Windows2000 "中的"Windows "。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始 |
(?<=pattern) | 反向肯定预查,与正向肯定预查类拟,只是方向相反。例如,“(?<=95|98|NT|2000)Windows "能匹配"2000Windows "中的"Windows ",但不能匹配"3.1Windows "中的"Windows "。 |
(?<!pattern) | 反向否定预查,与正向否定预查类拟,只是方向相反。例如“(?<!95|98|NT|2000)Windows "能匹配"3.1Windows "中的"Windows ",但不能匹配"2000Windows "中的"Windows "。 |
x|y | 匹配x或y。例如,“z|food "能匹配"z "或"food "。"(z|f)ood "则匹配"zood "或"food "。 |
[xyz] | 字符集合。匹配所包含的任意一个字符。例如,“[abc] "可以匹配"plain "中的"a "。 |
[^xyz] | 负值字符集合(补集)。匹配未包含的任意字符。例如,“[^abc] "可以匹配"plain "中的"p "。 |
[a-z] | 字符范围。匹配指定范围内的任意字符。例如,“[a-z] "可以匹配"a "到"z "范围内的任意小写字母字符。 |
[^a-z] | 负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z] "可以匹配任何不在"a "到"z "范围内的任意字符。 |
\b | 匹配一个单词边界,也就是指单词和空格间的位置。例如,“er\b "可以匹配"never "中的"er ",但不能匹配"verb "中的"er "。 |
\B | 匹配非单词边界。“er\B "能匹配"verb "中的"er ",但不能匹配"never "中的"er "。 |
\cx | 匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c "字符。 |
\d | 匹配一个数字字符。等价于[0-9]。 |
\D | 匹配一个非数字字符。等价于[^0-9]。 |
\f | 匹配一个换页符。等价于\x0c和\cL。 |
\n | 匹配一个换行符。等价于\x0a和\cJ。 |
\r | 匹配一个回车符。等价于\x0d和\cM。 |
\s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。 |
\S | 匹配任何非空白字符。等价于[^ \f\n\r\t\v]。 |
\t | 匹配一个制表符。等价于\x09和\cI。 |
\v | 匹配一个垂直制表符。等价于\x0b和\cK。 |
\w | 匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_] "。 |
\W | 匹配任何非单词字符。等价于“[^A-Za-z0-9_] "。 |
\xn | 匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,“\x41 "匹配"A "。"\x041 "则等价于"\x04&1 "。正则表达式中可以使用ASCII编码。. |
\num | 匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\1 "匹配两个连续的相同字符。 |
\n | 标识一个八进制转义值或一个向后引用。如果\n之前至少n个获取的子表达式,则n为向后引用。否则,如果n为八进制数字(0-7),则n为一个八进制转义值。 |
\nm | 标识一个八进制转义值或一个向后引用。如果\nm之前至少有nm个获得子表达式,则nm为向后引用。如果\nm之前至少有n个获取,则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若n和m均为八进制数字(0-7),则\nm将匹配八进制转义值nm。 |
\nml | 如果n为八进制数字(0-3),且m和l均为八进制数字(0-7),则匹配八进制转义值nml。 |
\un | 匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号(©)。 |
3)POSIX字符类
Perl也支持POSIX字符类的匹配(关于POSIX字符类的详细说明见第二部分),表示语法:[:class:], 在pattern中则必须写为"[[:class:]]"。
在Perl中,"[[^:class:]]"表示posix指定类的补集,这种情况下也可略去[::],在类名前加'^'表示为"[^class]"。
二、POSIX规范正则表达式
POSIX的全称是Portable Operating System Interface for uniX,它由一系列规范构成,定义了UNIX操作系统应当支持的功能,所以“POSIX规范的正则表达式”其实只是“关于正则表达式的POSIX规范”,它定义了BRE(Basic Regular Expression,基本型正则表达式)和ERE(Extended Regular Express,扩展型正则表达式)。在兼容POSIX的UNIX系统上,vi/vim, awk, grep和sed之类的工具都遵循POSIX规范,一些数据库系统中的正则表达式也符合POSIX规范。
1)BRE
在Linux/Unix常用工具中,grep、vi、sed都属于BRE这一派,它的语法看起来比较奇怪,元字符『(』、『)』、『{』、『}』必 须转义之后才具有特殊含义,所以正则表达式『(a)b』只能匹配字符串 (a)b而不是字符串ab;
正则表达式『a{1,2}』只能匹配字符串a{1,2},正则表达式『a\{1,2\}』才能匹配字符串a或者aa。
之所以这么麻烦,是因为这些工具的诞生时间很早,正则表达式的许多功能却是逐步发展演化出来的,之前这些元字符可能并没有特殊的含义;为保证向后兼容,就只能使用转义。而且有些功能甚至根本就不支持,
比如BRE就不支持『+』和『?』量词,也不支持多选结构『(…|…)』和反向引用『\1』、 『\2』…
不过在今天,纯粹的BRE已经很少见了,毕竟大家已经认为正则表达式“理所应当”支持多选结构和反向引用等功能,没有确实太不方便。所以虽然vi属于 BRE流派,但提供了这些功能。
GNU也对BRE做了扩展,支持『+』、『?』、『|』,只是使用时必须写成『\+』、『\?』、『\|』,而且也支持 『\1』、『\2』之类反向引用。这样,GNU的grep等工具虽然名义上属于BRE流,但更确切的名称是GNU BRE。
2)ERE
在Linux/Unix常用工具中,egrep、awk则属于ERE这一派。虽然BRE名为“基本”而ERE名为“扩展”,但ERE并不要求兼容 BRE的语法,而是自成一体。因此其中的元字符不用转义(在元字符之前添加反斜线会取消其特殊含义),
所以『(ab|cd)』就可以匹配字符串ab或者 cd,量词『+』、『?』、『{n,m}』可以直接使用。ERE并没有明确规定支持反向引用,但是不少工具都支持『\1』、『\2』之类的反向引用。
GNU出品的egrep等工具就属于ERE流(更准确的名字是GNU ERE),但因为GNU已经对BRE做了不少扩展,所谓的GNU ERE其实只是个说法而已,它有的功能GNU BRE都有了,只是元字符不需要转义而已。
下面的表格简要说明了几种POSIX流派的区别(其实,现在的BRE和ERE在功能上并没有什么区别,主要的差异是在元字符的转义上)。
3)POSIX字符组
在某些文档中,你还会发现类似『[:digit:]』、『[:lower:]』之类的表示法,它们看起来不难理解(digit就是“数 字”,lower就是“小写”),但又很奇怪,这就是POSIX字符组。不仅在Linux/Unix的常见工具中,甚至一些编程语言中都出现了这些字符组,
为避免困惑,这里有必要简要介绍它们。
在POSIX规范中,『[a-z]』、『[aeiou]』之类的记法仍然是合法的,其意义与PCRE中的字符组也没有区别,只是这类记法的准确名称 是POSIX方括号表达式(bracket expression),它主要用在Unix/Linux系统中。
POSIX方括号表示法与PCRE字符组的最主要差别在于:POSIX字符组中,反斜 线\不是用来转义的。所以POSIX方括号表示法『[\d]』只能匹配\和d两个字符,而不是『[0-9]』对应的数字字符。
为了解决字符组中特殊意义字符的转义问题,POSIX方括号表示法规定,如果要在字符组中表达字符](而不是作为字符组的结束标记),应当让它紧跟 在字符组的开方括号之后,所以POSIX中,
正则表达式『[]a]』能匹配的字符就是]和a;如果要在POSIX方括号表示法中表达字符-(而不是范围表 示法),必须将它紧挨在闭方括号]之前,所以『[a-]』能匹配的字符就是a和-。
POSIX规范也定义了POSIX字符组,它近似等价于于PCRE的字符组简记法,用一个有直观意义的名字来表示某一组字符,比如digit表示“数字字符”,alpha表示“字母字符”。
不过,POSIX中还有一个值得注意的概念:locale(通常翻译为“语言环境”)。它是一组与语言和文化相关的设定,包括日期格式、货币币值、 字符编码等等。POSIX字符组的意义会根据locale的变化而变化,
下面表介绍了常见的POSIX字符组在ASCII语言环境与Unicode语言环境下的意义:
POSIX字符组 | 说明 | ASCII语言环境 | Unicode语言环境 |
[:alnum:]* | 字母字符和数字字符 | [a-zA-Z0-9] | [\p{L&}\p{Nd}] |
[:alpha:] | 字母 | [a-zA-Z] | \p{L&} |
[:ascii:] | ASCII字符 | [\x00-\x7F] | \p{InBasicLatin} |
[:blank:] | 空格字符和制表符 | [ \t] | [\p{Zs}\t] |
[:cntrl:] | 控制字符 | [\x00-\x1F\x7F] | \p{Cc} |
[:digit:] | 数字字符 | [0-9] | \p{Nd} |
[:graph:] | 空白字符之外的字符 | [\x21-\x7E] | [^\p{Z}\p{C}] |
[:lower:] | 小写字母字符 | [a-z] | \p{Ll} |
[:print:] | 类似[:graph:],但包括空白字符 | [\x20-\x7E] | \P{C} |
[:punct:] | 标点符号 | [][!"#$%&'()*+,./:;<=>?@\^_`{|}~-] | [\p{P}\p{S}] |
[:space:] | 空白字符 | [ \t\r\n\v\f] | [\p{Z}\t\r\n\v\f] |
[:upper:] | 大写字母字符 | [A-Z] | \p{Lu} |
[:word:]* | 字母字符 | [A-Za-z0-9_] | [\p{L}\p{N}\p{Pc}] |
[:xdigit:] | 十六进制字符 | [A-Fa-f0-9] | [A-Fa-f0-9] |
注:标记*的字符组简记法并不是POSIX规范所要求的,但使用很多,一般语言中都提供,文档中也会出现。
POSIX字符组的使用有所不同。主要区别在于,PCRE字符组简记法可以脱离方括号直接出现,而POSIX字符组必须出现在方括号内,所以同样是匹配数字字符,单独出现时,PCRE中可以直接写『\d』,而POSIX字符组就必须写成『[[:digit:]]』。
Linux/Unix下的工具中,一般都可以直接使用POSIX字符组,而PCRE的字符组简记法『\w』、『\d』等则大多不支持,所以如果你看到『[[:space:]]』而不是『\s』,一定不要感到奇怪。
不过,在常用的编程语言中,Java、PHP、Ruby也支持使用POSIX字符组。其中Java和PHP中的POSIX字符组都是按照ASCII 语言环境进行匹配;
Ruby的情况则要复杂一点,Ruby 1.8按照ASCII语言环境进行匹配,而且不支持『[:word:]』和『[:alnum:]』,Ruby 1.9按照Unicode语言环境进行匹配,同时支持『[:word:]』和『[:alnum:]』。
4)几种POSIX规范正则表达式的比较
流派 | 说明 | 工具 |
BRE | (、)、{、}都必须转义使用,不支持+、?、| | grep、sed、vi(但vi支持这些多选结构和反向引用) |
GNU BRE | (、)、{、}、+、?、|都必须转义使用 | GNU grep、GNU sed |
ERE | 元字符不必转义,+、?、(、)、{、}、|可以直接使用,\1、\2的支持不确定 | egrep、awk |
GNU ERE | 元字符不必转义,+、?、(、)、{、}、|可以直接使用,支持\1、\2 | grep –E、GNU awk |
三、对比
由上面两节我们看到,PCRE和POSIX RE的区别大多在于对元字符的选择和转义使用,除此之外他们还在以下几个方面有所不同:
1)定界符:
POSIX兼容正则没有定界符,函数的相应参数会被认为是正则。
PERL兼容正则可以使用任何不是字母、数字或反斜线(\)的字符作为定界符,如果作为定界符的字符必须被用在表达式本身中,则需要用反斜线转义。也可以使用(),{},[] 和 <> 作为定界符
2)修饰符:
POSIX兼容正则没有修饰符。
PERL兼容正则中可能使用的修饰符(修饰符中的空格和换行被忽略,其它字符会导致错误):
i (PCRE_CASELESS):匹配时忽略大小写。
m(PCRE_MULTILINE):当设定了此修饰符,行起始(^)和行结束($)除了匹配整个字符串开头和结束外,还分别匹配其中的换行符(\n)的之后和之前。
s(PCRE_DOTALL):如果设定了此修饰符,模式中的圆点元字符(.)匹配所有的字符,包括换行符。没有此设定的话,则不包括换行符。
x(PCRE_EXTENDED):如果设定了此修饰符,模式中的空白字符除了被转义的或在字符类中的以外完全被忽略。
A(PCRE_ANCHORED):如果设定了此修饰符,模式被强制为“anchored”,即强制仅从目标字符串的开头开始匹配。
D(PCRE_DOLLAR_ENDONLY):如果设定了此修饰符,模式中的行结束($)仅匹配目标字符串的结尾。没有此选项时,如果最后一个字符是换行符的话,也会被匹配在里面。如果设定了 m 修饰符则忽略此选项。
U(PCRE_UNGREEDY):使“?”的默认匹配成为贪婪状态的。
X(PCRE_EXTRA):模式中的任何反斜线后面跟上一个没有特殊意义的字母导致一个错误,从而保留此组合以备将来扩充。默认情况下,一个反斜线后面跟一个没有特殊意义的字母被当成该字母本身。
u(PCRE_UTF8):模式字符串被当成UTF-8。
3)关于元字符『.』的解释:
. PERL兼容正则匹配除了换行符外的任意一个字符
. POSIX兼容正则匹配任意一个字符
最后以常用Linux/Unix工具中的表示法来举例说明他们之间的转义情况,其中的工具GNU的版本为准:
PCRE记法 | vi/vim | grep | awk | sed |
* | * | * | * | * |
+ | \+ | \+ | + | \+ |
? | \= | \? | ? | \? |
{m,n} | \{m,n} | \{m,n\} | {m,n} | \{m,n\} |
\b * | \< \> | \< \> | \< \> | \y \< \> |
(…|…) | \(…\|…\) | \(…\|…\) | (…|…) | (…|…) |
(…) | \(…\) | \(…\) | (…) | (…) |
\1 \2 | \1 \2 | \1 \2 | 不支持 | \1 \2 |
注:PCRE中常用\b来表示“单词的起始或结束位置”,但Linux/Unix的工具中,通常用\<来匹配“单词的起始位置”,用\>来匹配“单词的结束位置”,sed中的\y可以同时匹配这两个位置。
参考文章:
http://www.css119.com/book/RegExp/
http://www.chinaunix.net/old_jh/25/159388.html
http://www.infoq.com/cn/news/2011/07/regular-expressions-6-POSIX
http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap09.html