引擎继续匹配下一个记号“>”。
这次得到了一个成功匹配。
引擎于是报告“”是一个成功的匹配。
整个过程大致如此。
∙ 惰性扩展的一个替代方案
我们还有一个更好的替代方案。
可以用一个贪婪重复与一个取反字符集:
“<[^>]+>”。
之所以说这是一个更好的方案在于使用惰性重复时,引擎会在找到一个成功匹配前对每一个字符进行回溯。
而使用取反字符集则不需要进行回溯。
最后要记住的是,本教程仅仅谈到的是正则导向的引擎。
文本导向的引擎是不回溯的。
但是同时他们也不支持惰性重复操作。
7. 使用“.”匹配几乎任意字符
在正则表达式中,“.”是最常用的符号之一。
不幸的是,它也是最容易被误用的符号之一。
“.”匹配一个单个的字符而不用关心被匹配的字符是什么。
唯一的例外是新行符。
在本教程中谈到的引擎,缺省情况下都是不匹配新行符的。
因此在缺省情况下,“.”等于是字符集[^\n\r](Window)或[^\n](Unix)的简写。
这个例外是因为历史的原因。
因为早期使用正则表达式的工具是基于行的。
它们都是一行一行的读入一个文件,将正则表达式分别应用到每一行上去。
在这些工具中,字符串是不包含新行符的。
因此“.”也就从不匹配新行符。
现代的工具和语言能够将正则表达式应用到很大的字符串甚至整个文件上去。
本教程讨论的所有正则表达式实现都提供一个选项,可以使“.”匹配所有的字符,包括新行符。
在RegexBuddy,EditPadPro或PowerGREP等工具中,你可以简单的选中“点号匹配新行符”。
在Perl中,“.”可以匹配新行符的模式被称作“单行模式”。
很不幸,这是一个很容易混淆的名词。
因为还有所谓“多行模式”。
多行模式只影响行首行尾的锚定(anchor),而单行模式只影响“.”。
其他语言和正则表达式库也采用了Perl的术语定义。
当在.NETFramework中使用正则表达式类时,你可以用类似下面的语句来激活单行模式:
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”中的任何字符。
类似的,$匹配字符串中最后一个字符的后面的位置。
所以<>匹配“abc”中的c。
∙ 锚定的应用
在编程语言中校验用户输入时,使用锚定是非常重要的。
如果你想校验用户的输入为整数,用<<^\d+$>>。
用户输入中,常常会有多余的前导空格或结束空格。
你可以用<<^\s*>>和<<\s*$>>来匹配前导空格或结束空格。
∙ 使用“^”和“$”作为行的开始和结束锚定
如果你有一个包含了多行的字符串。
例如:
“firstline\n\rsecondline”(其中\n\r表示一个新行符)。
常常需要对每行分别处理而不是整个字符串。
因此,几乎所有的正则表达式引擎都提供一个选项,可以扩展这两种锚定的含义。
“^”可以匹配字串的开始位置(在f之前),以及每一个新行符的后面位置(在\n\r和s之间)。
类似的,$会匹配字串的结束位置(最后一个e之后),以及每个新行符的前面(在e与\n\r之间)。
在.NET中,当你使用如下代码时,将会定义锚定匹配每一个新行符的前面和后面位置:
Regex.Match("string","regex",RegexOptions.Multiline)
应用:
stringstr=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>>应用到字符串“Thisislandisbeautiful”。
引擎先处理符号<<\b>>。
因为\b是0长度,所以第一个字符T前面的位置会被考察。
因为T是一个“单词字符”,而它前面的字符是一个空字符(void),所以\b匹配了单词边界。
接着<>和第一个字符“T”匹配失败。
匹配过程继续进行,直到第五个空格符,和第四个字符“s”之间又匹配了<<\b>>。
然而空格符和<>不匹配。
继续向后,到了第六个字符“i”,和第五个空格字符之间匹配了<<\b>>,然后<>和第六、第七个字符都匹配了。
然而第八个字符和第二个“单词边界”不匹配,所以匹配又失败了。
到了第13个字符i,因为和前面一个空格符形成“单词边界”,同时<>和“is”匹配。
引擎接着尝试匹配第二个<<\b>>。
因为第15个空格符和“s”形成单词边界,所以匹配成功。
引擎“急着”返回成功匹配的结果。
10. 选择符
正则表达式中“|”表示选择。
你可以用选择符匹配多个可能的正则表达式中的一个。
如果你想搜索文字“cat”或“dog”,你可以用<>。
如果你想有更多的选择,你只要扩展列表<>。
选择符在正则表达式中具有最低的优先级,也就是说,它告诉引擎要么匹配选择符左边的所有表达式,要么匹配右边的所有表达式。
你也可以用圆括号来限制选择符的作用范围。
如<<\b(cat|dog)\b>>,这样告诉正则引擎把(cat|dog)当成一个正则表达式单位来处理。
∙ 注意正则引擎的“急于表功”性
正则引擎是急切的,当它找到一个有效的匹配时,它会停止搜索。
因此在一定条件下,选择符两边的表达式的顺序对结果会有影响。
假设你想用正则表达式搜索一个编程语言的函数列表:
Get,GetValue,Set或SetValue。
一个明显的解决方案是<>。
让我们看看当搜索SetValue时的结果。
因为<>和<>都失败了,而<>匹配成功。
因为正则导向的引擎都是“急切”的,所以它会返回第一个成功的匹配,就是“Set”,而不去继续搜索是否有其他更好的匹配。
和我们期望的相反,正则表达式并没有匹配整个字符串。
有几种可能的解决办法。
一是考虑到正则引擎的“急切”性,改变选项的顺序,例如我们使用<>,这样我们就可以优先搜索最长的匹配。
我们也可以把四个选项结合起来成两个选项:
<|Set(Value)?
>>。
因为问号重复符是贪婪的,所以SetValue总会在Set之前被匹配。
一个更好的方案是使用单词边界:
<<\b(