本文是Jan Goyvaerts为RegexBuddy写的教程的译文,版权归原做者全部,欢迎转载。可是为了尊重原做者和译者的劳动,请注明出处!谢谢!程序员
1. 什么是正则表达式正则表达式
基本说来,正则表达式是一种用来描述必定数量文本的模式。Regex表明Regular Express。本文将用<<regex>>来表示一段具体的正则表达式。编程
一段文本就是最基本的模式,简单的匹配相同的文本。缓存
2. 不一样的正则表达式引擎编程语言
正则表达式引擎是一种能够处理正则表达式的软件。一般,引擎是更大的应用程序的一部分。在软件世界,不一样的正则表达式并不互相兼容。本教程会集中讨论Perl 5 类型的引擎,由于这种引擎是应用最普遍的引擎。同时咱们也会提到一些和其余引擎的区别。许多近代的引擎都很相似,但不彻底同样。例如.NET正则库,JDK正则包。编辑器
3. 文字符号函数
最基本的正则表达式由单个文字符号组成。如<<a>>,它将匹配字符串中第一次出现的字符“a”。如对字符串“Jack is a boy”。“J”后的“a”将被匹配。而第二个“a”将不会被匹配。工具
正则表达式也能够匹配第二个“a”,这必须是你告诉正则表达式引擎从第一次匹配的地方开始搜索。在文本编辑器中,你可使用“查找下一个”。在编程语言中,会有一个函数可使你从前一次匹配的位置开始继续向后搜索。测试
相似的,<<cat>>会匹配“About cats and dogs”中的“cat”。这等因而告诉正则表达式引擎,找到一个<<c>>,紧跟一个<<a>>,再跟一个<<t>>。优化
要注意,正则表达式引擎缺省是大小写敏感的。除非你告诉引擎忽略大小写,不然<<cat>>不会匹配“Cat”。
· 特殊字符
对于文字字符,有11个字符被保留做特殊用途。他们是:
[ ] " ^ $ . | ? * + ( )
这些特殊字符也被称做元字符。
若是你想在正则表达式中将这些字符用做文本字符,你须要用反斜杠“"”对其进行换码 (escape)。例如你想匹配“1+1=2”,正确的表达式为<<1"+1=2>>.
须要注意的是,<<1+1=2>>也是有效的正则表达式。但它不会匹配“1+1=2”,而会匹配“123+111=234”中的“111=2”。由于“+”在这里表示特殊含义(重复1次到屡次)。
在编程语言中,要注意,一些特殊的字符会先被编译器处理,而后再传递给正则引擎。所以正则表达式<<1"+2=2>>在C++中要写成“1""+1=2”。为了匹配“C:"temp”,你要用正则表达式<<C:""temp>>。而在C++中,正则表达式则变成了“C:""""temp”。
· 不可显示字符
可使用特殊字符序列来表明某些不可显示字符:
<<"t>>表明Tab(0x09)
<<"r>>表明回车符(0x0D)
<<"n>>表明换行符(0x0A)
要注意的是Windows中文本文件使用“"r"n”来结束一行而Unix使用“"n”。
4. 正则表达式引擎的内部工做机制
知道正则表达式引擎是如何工做的有助于你很快理解为什么某个正则表达式不像你指望的那样工做。
有两种类型的引擎:文本导向(text-directed)的引擎和正则导向(regex-directed)的引擎。Jeffrey Friedl把他们称做DFA和NFA引擎。本文谈到的是正则导向的引擎。这是由于一些很是有用的特性,如“惰性”量词(lazy quantifiers)和反向引用(backreferences),只能在正则导向的引擎中实现。因此绝不意外这种引擎是目前最流行的引擎。
你能够轻易分辨出所使用的引擎是文本导向仍是正则导向。若是反向引用或“惰性”量词被实现,则能够确定你使用的引擎是正则导向的。你能够做以下测试:将正则表达式<<regex|regex not>>应用到字符串“regex not”。若是匹配的结果是regex,则引擎是正则导向的。若是结果是regex not,则是文本导向的。由于正则导向的引擎是“猴急”的,它会很急切的进行表功,报告它找到的第一个匹配 。
· 正则导向的引擎老是返回最左边的匹配
这是须要你理解的很重要的一点:即便之后有可能发现一个“更好”的匹配,正则导向的引擎也老是返回最左边的匹配。
当把<<cat>>应用到“He captured a catfish for his cat”,引擎先比较<<c>>和“H”,结果失败了。因而引擎再比较<<c>>和“e”,也失败了。直到第四个字符,<<c>>匹配了“c”。<<a>>匹配了第五个字符。到第六个字符<<t>>没能匹配“p”,也失败了。引擎再继续从第五个字符从新检查匹配性。直到第十五个字符开始,<<cat>>匹配上了“catfish”中的“cat”,正则表达式引擎急切的返回第一个匹配的结果,而不会再继续查找是否有其余更好的匹配。
5. 字符集
字符集是由一对方括号“[]”括起来的字符集合。使用字符集,你能够告诉正则表达式引擎仅仅匹配多个字符中的一个。若是你想匹配一个“a”或一个“e”,使用<<[ae]>>。你可使用<<gr[ae]y>>匹配gray或grey。这在你不肯定你要搜索的字符是采用美国英语仍是英国英语时特别有用。相反,<<gr[ae]y>>将不会匹配graay或graey。字符集中的字符顺序并无什么关系,结果都是相同的。
你可使用连字符“-”定义一个字符范围做为字符集。<<[0-9]>>匹配0到9之间的单个数字。你可使用不止一个范围。<<[0-9a-fA-F] >>匹配单个的十六进制数字,而且大小写不敏感。你也能够结合范围定义与单个字符定义。<<[0-9a-fxA-FX]>>匹配一个十六进制数字或字母X。再次强调一下,字符和范围定义的前后顺序对结果没有影响。
· 字符集的一些应用
查找一个可能有拼写错误的单词,好比<<sep[ae]r[ae]te>> 或 <<li[cs]en[cs]e>>。
查找程序语言的标识符,<<A-Za-z_][A-Za-z_0-9]*>>。(*表示重复0或屡次)
查找C风格的十六进制数<<0[xX][A-Fa-f0-9]+>>。(+表示重复一次或屡次)
· 取反字符集
在左方括号“[”后面紧跟一个尖括号“^”,将会对字符集取反。结果是字符集将匹配任何不在方括号中的字符。不像“.”,取反字符集是能够匹配回车换行符的。
须要记住的很重要的一点是,取反字符集必需要匹配一个字符。<<q[^u]>>并不意味着:匹配一个q,后面没有u跟着。它意味着:匹配一个q,后面跟着一个不是u的字符。因此它不会匹配“Iraq”中的q,而会匹配“Iraq is a country”中的q和一个空格符。事实上,空格符是匹配中的一部分,由于它是一个“不是u的字符”。
若是你只想匹配一个q,条件是q后面有一个不是u的字符,咱们能够用后面将讲到的向前查看来解决。
· 字符集中的元字符
须要注意的是,在字符集中只有4个 字符具备特殊含义。它们是:“] " ^ -”。“]”表明字符集定义的结束;“"”表明转义;“^”表明取反;“-”表明范围定义。其余常见的元字符在字符集定义内部都是正常字符,不须要转义。例如,要搜索星号*或加号+,你能够用<<[+*]>>。固然,若是你对那些一般的元字符进行转义,你的正则表达式同样会工做得很好,可是这会下降可读性。
在字符集定义中为了将反斜杠“"”做为一个文字字符而非特殊含义的字符,你须要用另外一个反斜杠对它进行转义。<<[""x]>>将会匹配一个反斜杠和一个X。“]^-”均可以用反斜杠进行转义,或者将他们放在一个不可能使用到他们特殊含义的位置。咱们推荐后者,由于这样能够增长可读性。好比对于字符“^”,将它放在除了左括号“[”后面的位置,使用的都是文字字符含义而非取反含义。如<<[x^]>>会匹配一个x或^。<<[]x]>>会匹配一个“]”或“x”。<<[-x]>>或<<[x-]>>都会匹配一个“-”或“x”。
· 字符集的简写
由于一些字符集很是经常使用,因此有一些简写方式。
<<"d>>表明<<[0-9]>>;
<<"w>>表明单词字符。这个是随正则表达式实现的不一样而有些差别。绝大多数的正则表达式实现的单词字符集都包含了<<A-Za-z0-9_]>>。
<<"s>>表明“白字符”。这个也是和不一样的实现有关的。在绝大多数的实现中,都包含了空格符和Tab符,以及回车换行符<<"r"n>>。
字符集的缩写形式能够用在方括号以内或以外。<<"s"d>>匹配一个白字符后面紧跟一个数字。<<["s"d]>>匹配单个白字符或数字。<<["da-fA-F]>>将匹配一个十六进制数字。
取反字符集的简写
<<["S]>> = <<[^"s]>>
<<["W]>> = <<[^"w]>>
<<["D]>> = <<[^"d]>>
· 字符集的重复
若是你用“?*+”操做符来重复一个字符集,你将会重复整个字符集。而不只是它匹配的那个字符。正则表达式<<[0-9]+>>会匹配837以及222。
若是你仅仅想重复被匹配的那个字符,能够用向后引用达到目的。咱们之后将讲到向后引用。
6. 使用?*或+ 进行重复
?:告诉引擎匹配前导字符0次或一次。事实上是表示前导字符是可选的。
+:告诉引擎匹配前导字符1次或屡次
*:告诉引擎匹配前导字符0次或屡次
<[A-Za-z][A-Za-z0-9]*>匹配没有属性的HTML标签,“<”以及“>”是文字符号。第一个字符集匹配一个字母,第二个字符集匹配一个字母或数字。
咱们彷佛也能够用<[A-Za-z0-9]+>。可是它会匹配<1>。可是这个正则表达式在你知道你要搜索的字符串不包含相似的无效标签时仍是足够有效的。
· 限制性重复
许多现代的正则表达式实现,都容许你定义对一个字符重复多少次。词法是:{min,max}。min和max都是非负整数。若是逗号有而max被忽略了,则max没有限制。若是逗号和max都被忽略了,则重复min次。
所以{0,}和*同样,{1,}和+ 的做用同样。
你能够用<<"b[1-9][0-9]{3}"b>>匹配1000~9999之间的数字(“"b”表示单词边界)。<<"b[1-9][0-9]{2,4}"b>>匹配一个在100~99999之间的数字。
· 注意贪婪性
假设你想用一个正则表达式匹配一个HTML标签。你知道输入将会是一个有效的HTML文件,所以正则表达式不须要排除那些无效的标签。因此若是是在两个尖括号之间的内容,就应该是一个HTML标签。
许多正则表达式的新手会首先想到用正则表达式<< <.+> >>,他们会很惊讶的发现,对于测试字符串,“This is a <EM>first</EM> test”,你可能指望会返回<EM>,而后继续进行匹配的时候,返回</EM>。
但事实是不会。正则表达式将会匹配“<EM>first</EM>”。很显然这不是咱们想要的结果。缘由在于“+”是贪婪的。也就是说,“+”会致使正则表达式引擎试图尽量的重复前导字符。只有当这种重复会引发整个正则表达式匹配失败的状况下,引擎会进行回溯。也就是说,它会放弃最后一次的“重复”,而后处理正则表达式余下的部分。
和“+”相似,“?*”的重复也是贪婪的。
· 深刻正则表达式引擎内部
让咱们来看看正则引擎如何匹配前面的例子。第一个记号是“<”,这是一个文字符号。第二个符号是“.”,匹配了字符“E”,而后“+”一直能够匹配其他的字符,直到一行的结束。而后到了换行符,匹配失败(“.”不匹配换行符)。因而引擎开始对下一个正则表达式符号进行匹配。也即试图匹配“>”。到目前为止,“<.+”已经匹配了“<EM>first</EM> test”。引擎会试图将“>”与换行符进行匹配,结果失败了。因而引擎进行回溯。结果是如今“<.+”匹配“<EM>first</EM> tes”。因而引擎将“>”与“t”进行匹配。显然仍是会失败。这个过程继续,直到“<.+”匹配“<EM>first</EM”,“>”与“>”匹配。因而引擎找到了一个匹配“<EM>first</EM>”。记住,正则导向的引擎是“急切的”,因此它会急着报告它找到的第一个匹配。而不是继续回溯,即便可能会有更好的匹配,例如“<EM>”。因此咱们能够看到,因为“+”的贪婪性,使得正则表达式引擎返回了一个最左边的最长的匹配。
· 用懒惰性取代贪婪性
一个用于修正以上问题的可能方案是用“+”的惰性代替贪婪性。你能够在“+”后面紧跟一个问号“?”来达到这一点。“*”,“{}”和“?”表示的重复也能够用这个方案。所以在上面的例子中咱们可使用“<.+?>”。让咱们再来看看正则表达式引擎的处理过程。
再一次,正则表达式记号“<”会匹配字符串的第一个“<”。下一个正则记号是“.”。此次是一个懒惰的“+”来重复上一个字符。这告诉正则引擎,尽量少的重复上一个字符。所以引擎匹配“.”和字符“E”,而后用“>”匹配“M”,结果失败了。引擎会进行回溯,和上一个例子不一样,由于是惰性重复,因此引擎是扩展惰性重复而不是减小,因而“<.+”如今被扩展为“<EM”。引擎继续匹配下一个记号“>”。此次获得了一个成功匹配。引擎因而报告“<EM>”是一个成功的匹配。整个过程大体如此。
· 惰性扩展的一个替代方案
咱们还有一个更好的替代方案。能够用一个贪婪重复与一个取反字符集:“<[^>]+>”。之因此说这是一个更好的方案在于使用惰性重复时,引擎会在找到一个成功匹配前对每个字符进行回溯。而使用取反字符集则不须要进行回溯。
最后要记住的是,本教程仅仅谈到的是正则导向的引擎。文本导向的引擎是不回溯的。可是同时他们也不支持惰性重复操做。
7. 使用“.”匹配几乎任意字符
在正则表达式中,“.”是最经常使用的符号之一。不幸的是,它也是最容易被误用的符号之一。
“.”匹配一个单个的字符而不用关心被匹配的字符是什么。惟一的例外是新行符。在本教程中谈到的引擎,缺省状况下都是不匹配新行符的。所以在缺省状况下,“.”等因而字符集[^"n"r](Window)或[^"n]( Unix)的简写。
这个例外是由于历史的缘由。由于早期使用正则表达式的工具是基于行的。它们都是一行一行的读入一个文件,将正则表达式分别应用到每一行上去。在这些工具中,字符串是不包含新行符的。所以“.”也就从不匹配新行符。
现代的工具和语言可以将正则表达式应用到很大的字符串甚至整个文件上去。本教程讨论的全部正则表达式实现都提供一个选项,可使“.”匹配全部的字符,包括新行符。在RegexBuddy, EditPad Pro或PowerGREP等工具中,你能够简单的选中“点号匹配新行符”。在Perl中,“.”能够匹配新行符的模式被称做“单行模式”。很不幸,这是一个很容易混淆的名词。由于还有所谓“多行模式”。多行模式只影响行首行尾的锚定(anchor),而单行模式只影响“.”。
其余语言和正则表达式库也采用了Perl的术语定义。当在.NET Framework中使用正则表达式类时,你能够用相似下面的语句来激活单行模式:Regex.Match(“string”,”regex”,RegexOptions.SingleLine)
· 保守的使用点号“.”
点号能够说是最强大的元字符。它容许你偷懒:用一个点号,就能匹配几乎全部的字符。可是问题在于,它也经常会匹配不应匹配的字符。
我会以一个简单的例子来讲明。让咱们看看如何匹配一个具备“mm/dd/yy”格式的日期,可是咱们想容许用户来选择分隔符。很快能想到的一个方案是<<"d"d."d"d."d"d>>。看上去它能匹配日期“02/12/03”。问题在于02512703也会被认为是一个有效的日期。
<<"d"d[-/.]"d"d[-/.]"d"d>>看上去是一个好一点的解决方案。记住点号在一个字符集里不是元字符。这个方案远不够完善,它会匹配“99/99/99”。而<<[0-1]"d[-/.][0-3]"d[-/.]"d"d>>又更进一步。尽管他也会匹配“19/39/99”。你想要你的正则表达式达到如何完美的程度取决于你想达到什么样的目的。若是你想校验用户输入,则须要尽量的完美。若是你只是想分析一个已知的源,而且咱们知道没有错误的数据,用一个比较好的正则表达式来匹配你想要搜寻的字符就已经足够。
8. 字符串开始和结束的锚定
锚定和通常的正则表达式符号不一样,它不匹配任何字符。相反,他们匹配的是字符以前或以后的位置。“^”匹配一行字符串第一个字符前的位置。<<^a>>将会匹配字符串“abc”中的a。<<^b>>将不会匹配“abc”中的任何字符。
相似的,$匹配字符串中最后一个字符的后面的位置。因此<<c$>>匹配“abc”中的c。
· 锚定的应用
在编程语言中校验用户输入时,使用锚定是很是重要的。若是你想校验用户的输入为整数,用<<^"d+$>>。
用户输入中,经常会有多余的前导空格或结束空格。你能够用<<^"s*>>和<<"s*$>>来匹配前导空格或结束空格。
· 使用“^”和“$”做为行的开始和结束锚定
若是你有一个包含了多行的字符串。例如:“first line"n"rsecond line”(其中"n"r表示一个新行符)。经常须要对每行分别处理而不是整个字符串。所以,几乎全部的正则表达式引擎都提供一个选项,能够扩展这两种锚定的含义。“^”能够匹配字串的开始位置(在f以前),以及每个新行符的后面位置(在"n"r和s之间)。相似的,$会匹配字串的结束位置(最后一个e以后),以及每一个新行符的前面(在e与"n"r之间)。
在.NET中,当你使用以下代码时,将会定义锚定匹配每个新行符的前面和后面位置:Regex.Match("string", "regex", RegexOptions.Multiline)
应用:string str = Regex.Replace(Original, "^", "> ", RegexOptions.Multiline)--将会在每行的行首插入“> ”。
· 绝对锚定
<<"A>>只匹配整个字符串的开始位置,<<"Z>>只匹配整个字符串的结束位置。即便你使用了“多行模式”,<<"A>>和<<"Z>>也从不匹配新行符。
即便"Z和$只匹配字符串的结束位置,仍然有一个例外的状况。若是字符串以新行符结束,则"Z和$将会匹配新行符前面的位置,而不是整个字符串的最后面。这个“改进”是由Perl引进的,而后被许多的正则表达式实现所遵循,包括Java,.NET等。若是应用<<^[a-z]+$>>到“joe"n”,则匹配结果是“joe”而不是“joe"n”。
9. 单词边界
元字符<<"b>>也是一种对位置进行匹配的“锚”。这种匹配是0长度匹配。
有4种位置被认为是“单词边界”:
1) 在字符串的第一个字符前的位置(若是字符串的第一个字符是一个“单词字符”)
2) 在字符串的最后一个字符后的位置(若是字符串的最后一个字符是一个“单词字符”)
3) 在一个“单词字符”和“非单词字符”之间,其中“非单词字符”紧跟在“单词字符”以后
4) 在一个“非单词字符”和“单词字符”之间,其中“单词字符”紧跟在“非单词字符”后面
“单词字符”是能够用“"w”匹配的字符,“非单词字符”是能够用“"W”匹配的字符。在大多数的正则表达式实现中,“单词字符”一般包括<<[a-zA-Z0-9_]>>。
例如:<<"b4"b>>可以匹配单个的4而不是一个更大数的一部分。这个正则表达式不会匹配“44”中的4。
换种说法,几乎能够说<<"b>>匹配一个“字母数字序列”的开始和结束的位置。
“单词边界”的取反集为<<"B>>,他要匹配的位置是两个“单词字符”之间或者两个“非单词字符”之间的位置。
· 深刻正则表达式引擎内部
让咱们看看把正则表达式<<"bis"b>>应用到字符串“This island is beautiful”。引擎先处理符号<<"b>>。由于"b是0长度 ,因此第一个字符T前面的位置会被考察。由于T是一个“单词字符”,而它前面的字符是一个空字符(void),因此"b匹配了单词边界。接着<<i>>和第一个字符“T”匹配失败。匹配过程继续进行,直到第五个空格符,和第四个字符“s”之间又匹配了<<"b>>。然而空格符和<<i>>不匹配。继续向后,到了第六个字符“i”,和第五个空格字符之间匹配了<<"b>>,而后<<is>>和第6、第七个字符都匹配了。然而第八个字符和第二个“单词边界”不匹配,因此匹配又失败了。到了第13个字符i,由于和前面一个空格符造成“单词边界”,同时<<is>>和“is”匹配。引擎接着尝试匹配第二个<<"b>>。由于第15个空格符和“s”造成单词边界,因此匹配成功。引擎“急着”返回成功匹配的结果。
10. 选择符
正则表达式中“|”表示选择。你能够用选择符匹配多个可能的正则表达式中的一个。
若是你想搜索文字“cat”或“dog”,你能够用<<cat|dog>>。若是你想有更多的选择,你只要扩展列表<<cat|dog|mouse|fish>>。
选择符在正则表达式中具备最低的优先级,也就是说,它告诉引擎要么匹配选择符左边的全部表达式,要么匹配右边的全部表达式。你也能够用圆括号来限制选择符的做用范围。如<<"b(cat|dog)"b>>,这样告诉正则引擎把(cat|dog)当成一个正则表达式单位来处理。
· 注意正则引擎的“急于表功”性
正则引擎是急切的,当它找到一个有效的匹配时,它会中止搜索。所以在必定条件下,选择符两边的表达式的顺序对结果会有影响。假设你想用正则表达式搜索一个编程语言的函数列表:Get,GetValue,Set或SetValue。一个明显的解决方案是<<Get|GetValue|Set|SetValue>>。让咱们看看当搜索SetValue时的结果。
由于<<Get>>和<<GetValue>>都失败了,而<<Set>>匹配成功。由于正则导向的引擎都是“急切”的,因此它会返回第一个成功的匹配,就是“Set”,而不去继续搜索是否有其余更好的匹配。
和咱们指望的相反,正则表达式并无匹配整个字符串。有几种可能的解决办法。一是考虑到正则引擎的“急切”性,改变选项的顺序,例如咱们使用<<GetValue|Get|SetValue|Set>>,这样咱们就能够优先搜索最长的匹配。咱们也能够把四个选项结合起来成两个选项:<<Get(Value)?|Set(Value)?>>。由于问号重复符是贪婪的,因此SetValue总会在Set以前被匹配。
一个更好的方案是使用单词边界:<<"b(Get|GetValue|Set|SetValue)"b>>或<<"b(Get(Value)?|Set(Value)?"b>>。更进一步,既然全部的选择都有相同的结尾,咱们能够把正则表达式优化为<<"b(Get|Set)(Value)?"b>>。
11. 组与向后引用
把正则表达式的一部分放在圆括号内,你能够将它们造成组。而后你能够对整个组使用一些正则操做,例如重复操做符。
要注意的是,只有圆括号“()”才能用于造成组。“[]”用于定义字符集。“{}”用于定义重复操做。
当用“()”定义了一个正则表达式组后,正则引擎则会把被匹配的组按照顺序编号,存入缓存。当对被匹配的组进行向后引用的时候,能够用“"数字”的方式进行引用。<<"1>>引用第一个匹配的后向引用组,<<"2>>引用第二个组,以此类推,<<"n>>引用第n个组。而<<"0>>则引用整个被匹配的正则表达式自己。咱们看一个例子。
假设你想匹配一个HTML标签的开始标签和结束标签,以及标签中间的文本。好比<B>This is a test</B>,咱们要匹配<B>和</B>以及中间的文字。咱们能够用以下正则表达式:“<([A-Z][A-Z0-9]*)[^>]*>.*?</"1>”
首先,“<”将会匹配“<B>”的第一个字符“<”。而后[A-Z]匹配B,[A-Z0-9]*将会匹配0到屡次字母数字,后面紧接着0到多个非“>”的字符。最后正则表达式的“>”将会匹配“<B>”的“>”。接下来正则引擎将对结束标签以前的字符进行惰性匹配,直到遇到一个“</”符号。而后正则表达式中的“"1”表示对前面匹配的组“([A-Z][A-Z0-9]*)”进行引用,在本例中,被引用的是标签名“B”。因此须要被匹配的结尾标签为“</B>”
你能够对相同的后向引用组进行屡次引用,<<([a-c])x"1x"1>>将匹配“axaxa”、“bxbxb”以及“cxcxc”。若是用数字形式引用的组没有有效的匹配,则引用到的内容简单的为空。
一个后向引用不能用于它自身。<<([abc]"1)>>是错误的。所以你不能将<<"0>>用于一个正则表达式匹配自己,它只能用于替换操做中。
后向引用不能用于字符集内部。<<(a)["1b]>>中的<<"1>>并不表示后向引用。在字符集内部,<<"1>>能够被解释为八进制形式的转码。
向后引用会下降引擎的速度,由于它须要存储匹配的组。若是你不须要向后引用,你能够告诉引擎对某个组不存储。例如:<<Get(?:Value)>>。其中“(”后面紧跟的“?:”会告诉引擎对于组(Value),不存储匹配的值以供后向引用。
· 重复操做与后向引用
当对组使用重复操做符时,缓存里后向引用内容会被不断刷新,只保留最后匹配的内容。例如:<<([abc]+)="1>>将匹配“cab=cab”,可是<<([abc])+="1>>却不会。由于([abc])第一次匹配“c”时,“"1”表明“c”;而后([abc])会继续匹配“a”和“b”。最后“"1”表明“b”,因此它会匹配“cab=b”。
应用:检查重复单词--当编辑文字时,很容易就会输入重复单词,例如“the the”。使用<<"b("w+)"s+"1"b>>能够检测到这些重复单词。要删除第二个单词,只要简单的利用替换功能替换掉“"1”就能够了。
· 组的命名和引用
在PHP,Python中,能够用<<(?P<name>group)>>来对组进行命名。在本例中,词法?P<name>就是对组(group)进行了命名。其中name是你对组的起的名字。你能够用(?P=name)进行引用。
.NET的命名组
.NET framework也支持命名组。不幸的是,微软的程序员们决定发明他们本身的语法,而不是沿用Perl、Python的规则。目前为止,尚未任何其余的正则表达式实现支持微软发明的语法。
下面是.NET中的例子:
(?<first>group)(?’second’group)
正如你所看到的,.NET提供两种词法来建立命名组:一是用尖括号“<>”,或者用单引号“’’”。尖括号在字符串中使用更方便,单引号在ASP代码中更有用,由于ASP代码中“<>”被用做HTML标签。
要引用一个命名组,使用"k<name>或"k’name’.
当进行搜索替换时,你能够用“${name}”来引用一个命名组。
12. 正则表达式的匹配模式
本教程所讨论的正则表达式引擎都支持三种匹配模式:
<</i>>使正则表达式对大小写不敏感,
<</s>>开启“单行模式”,即点号“.”匹配新行符
<</m>>开启“多行模式”,即“^”和“$”匹配新行符的前面和后面的位置。
· 在正则表达式内部打开或关闭模式
若是你在正则表达式内部插入修饰符(?ism),则该修饰符只对其右边的正则表达式起做用。(?-i)是关闭大小写不敏感。你能够很快的进行测试。<<(?i)te(?-i)st>>应该匹配TEst,可是不能匹配teST或TEST.
13. 原子组与防止回溯
在一些特殊状况下,由于回溯会使得引擎的效率极其低下。
让咱们看一个例子:要匹配这样的字串,字串中的每一个字段间用逗号作分隔符,第12个字段由P开头。
咱们容易想到这样的正则表达式<<^(.*?,){11}P>>。这个正则表达式在正常状况下工做的很好。可是在极端状况下,若是第12个字段不是由P开头,则会发生灾难性的回溯。如要搜索的字串为“1,2,3,4,5,6,7,8,9,10,11,12,13”。首先,正则表达式一直成功匹配直到第12个字符。这时,前面的正则表达式消耗的字串为“1,2,3,4,5,6,7,8,9,10,11,”,到了下一个字符,<<P>>并不匹配“12”。因此引擎进行回溯,这时正则表达式消耗的字串为“1,2,3,4,5,6,7,8,9,10,11”。继续下一次匹配过程,下一个正则符号为点号<<.>>,能够匹配下一个逗号“,”。然而<<,>>并不匹配字符“12”中的“1”。匹配失败,继续回溯。你们能够想象,这样的回溯组合是个很是大的数量。所以可能会形成引擎崩溃。
用于阻止这样巨大的回溯有几种方案:
一种简单的方案是尽量的使匹配精确。用取反字符集代替点号。例如咱们用以下正则表达式<<^([^,"r"n]*,){11}P>>,这样可使失败回溯的次数降低到11次。
另外一种方案是使用原子组。
原子组的目的是使正则引擎失败的更快一点。所以能够有效的阻止海量回溯。原子组的语法是<<(?>正则表达式)>>。位于(?>)之间的全部正则表达式都会被认为是一个单一的正则符号。一旦匹配失败,引擎将会回溯到原子组前面的正则表达式部分。前面的例子用原子组能够表达成<<^(?>(.*?,){11})P>>。一旦第十二个字段匹配失败,引擎回溯到原子组前面的<<^>>。
14. 向前查看与向后查看
Perl 5 引 入了两个强大的正则语法:“向前查看”和“向后查看”。他们也被称做“零长度断言”。他们和锚定同样都是零长度的(所谓零长度即指该正则表达式不消耗被匹 配的字符串)。不一样之处在于“先后查看”会实际匹配字符,只是他们会抛弃匹配只返回匹配结果:匹配或不匹配。这就是为何他们被称做“断言”。他们并不实 际消耗字符串中的字符,而只是断言一个匹配是否可能。
几乎本文讨论的全部正则表达式的实现都支持“向前向后查看”。惟一的一个例外是Javascript只支持向前查看。
· 确定和否认式的向前查看
如咱们前面提过的一个例子:要查找一个q,后面没有紧跟一个u。也就是说,要么q后面没有字符,要么后面的字符不是u。采用否认式向前查看后的一个解决方案为<<q(?!u)>>。否认式向前查看的语法是<<(?!查看的内容)>>。
确定式向前查看和否认式向前查看很相似:<<(?=查看的内容)>>。
若是在“查看的内容”部分有组,也会产生一个向后引用。可是向前查看自己并不会产生向后引用,也不会被计入向后引用的编号中。这是由于向前查看自己是会被抛弃掉的,只保留匹配与否的判断结果。若是你想保留匹配的结果做为向后引用,你能够用<<(?=(regex))>>来产生一个向后引用。
· 确定和否认式的前后查看
向后查看和向前查看有相同的效果,只是方向相反
否认式向后查看的语法是:<<(?<!查看内容)>>
确定式向后查看的语法是:<<(?<=查看内容)>>
咱们能够看到,和向前查看相比,多了一个表示方向的左尖括号。
例:<<(?<!a)b>>将会匹配一个没有“a”做前导字符的“b”。
值得注意的是:向前查看从当前字符串位置开始对“查看”正则表达式进行匹配;向后查看则从当前字符串位置开始前后回溯一个字符,而后再开始对“查看”正则表达式进行匹配。
· 深刻正则表达式引擎内部
让咱们看一个简单例子。
把正则表达式<<q(?!u)>>应用到字符串“Iraq”。正则表达式的第一个符号是<<q>>。正如咱们知道的,引擎在匹配<<q>>之前会扫过整个字符串。当第四个字符“q”被匹配后,“q”后面是空字符(void)。而下一个正则符号是向前查看。引擎注意到已经进入了一个向前查看正则表达式部分。下一个正则符号是<<u>>,和空字符不匹配,从而致使向前查看里的正则表达式匹配失败。由于是一个否认式的向前查看,意味着整个向前查看结果是成功的。因而匹配结果“q”被返回了。
咱们在把相同的正则表达式应用到“quit”。<<q>>匹配了“q”。下一个正则符号是向前查看部分的<<u>>,它匹配了字符串中的第二个字符“i”。引擎继续走到下个字符“i”。然而引擎这时注意到向前查看部分已经处理完了,而且向前查看已经成功。因而引擎抛弃被匹配的字符串部分,这将致使引擎回退到字符“u”。
由于向前查看是否认式的,意味着查看部分的成功匹配致使了整个向前查看的失败,所以引擎不得不进行回溯。最后由于再没有其余的“q”和<<q>>匹配,因此整个匹配失败了。
为了确保你能清楚地理解向前查看的实现,让咱们把<<q(?=u)i>>应用到“quit”。<<q>>首先匹配“q”。而后向前查当作功匹配“u”,匹配的部分被抛弃,只返回能够匹配的判断结果。引擎从字符“i”回退到“u”。因为向前查当作功了,引擎继续处理下一个正则符号<<i>>。结果发现<<i>>和“u”不匹配。所以匹配失败了。因为后面没有其余的“q”,整个正则表达式的匹配失败了。
· 更进一步理解正则表达式引擎内部机制
让咱们把<<(?<=a)b>>应用到“thingamabob”。引擎开始处理向后查看部分的正则符号和字符串中的第一个字符。在这个例子中,向后查看告诉正则表达式引擎回退一个字符,而后查看是否有一个“a”被匹配。由于在“t”前面没有字符,因此引擎不能回退。所以向后查看失败了。引擎继续走到下一个字符“h”。再一次,引擎暂时回退一个字符并检查是否有个“a”被匹配。结果发现了一个“t”。向后查看又失败了。
向后查看继续失败,直到正则表达式到达了字符串中的“m”,因而确定式的向后查看被匹配了。由于它是零长度的,字符串的当前位置仍然是“m”。下一个正则符号是<<b>>,和“m”匹配失败。下一个字符是字符串中的第二个“a”。引擎向后暂时回退一个字符,而且发现<<a>>不匹配“m”。
在下一个字符是字符串中的第一个“b”。引擎暂时性的向后退一个字符发现向后查看被知足了,同时<<b>>匹配了“b”。所以整个正则表达式被匹配了。做为结果,正则表达式返回字符串中的第一个“b”。
· 向前向后查看的应用
咱们来看这样一个例子:查找一个具备6位字符的,含有“cat”的单词。
首先,咱们能够不用向前向后查看来解决问题,例如:
<< cat"w{3}|"wcat"w{2}|"w{2}cat"w|"w{3}cat>>
足够简单吧!可是当需求变成查找一个具备6-12位字符,含有“cat”,“dog”或“mouse”的单词时,这种方法就变得有些笨拙了。
咱们来看看使用向前查看的方案。在这个例子中,咱们有两个基本需求要知足:一是咱们须要一个6位的字符,二是单词含有“cat”。
知足第一个需求的正则表达式为<<"b"w{6}"b>>。知足第二个需求的正则表达式为<<"b"w*cat"w*"b>>。
把二者结合起来,咱们能够获得以下的正则表达式:
<<(?="b"w{6}"b)"b"w*cat"w*"b>>
具体的匹配过程留给读者。可是要注意的一点是,向前查看是不消耗字符的,所以当判断单词知足具备6个字符的条件后,引擎会从开始判断前的位置继续对后面的正则表达式进行匹配。
最后做些优化,能够获得下面的正则表达式:
<<"b(?="w{6}"b)"w{0,3}cat"w*>>
15. 正则表达式中的条件测试
条件测试的语法为<<(?ifthen|else)>>。“if”部分能够是向前向后查看表达式。若是用向前查看,则语法变为:<<(?(?=regex)then|else)>>,其中else部分是可选的。
若是if部分为true,则正则引擎会试图匹配then部分,不然引擎会试图匹配else部分。
须要记住的是,向前前后查看并不实际消耗任何字符,所以后面的then与else部分的匹配时从if测试前的部分开始进行尝试。
16. 为正则表达式添加注释
在正则表达式中添加注释的语法是:<<(?#comment)>>
例:为用于匹配有效日期的正则表达式添加注释:
(?#year)(19|20)"d"d[- /.](?#month)(0[1-9]|1[012])[- /.](?#day)(0[1-9]|[12][0-9]|3[01])