正则表达式大家熟悉吗?了解多少,在这里我们就正则表达式来深入的研究一下吧。本文将关于使用?*或+进行重复、使用“.”匹配几乎任意字符 、字符串开始和结束的锚定、单词边界四方面进行分析。
1.使用?*或+进行重复
?:告诉引擎匹配前导字符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之间的数字。
注意贪婪性假设你想用一个VB.NET正则表达式匹配一个HTML标签。你知道输入将会是一个有效的HTML文件,因此正则表达式不需要排除那些无效的标签。所以如果是在两个尖括号之间的内容,就应该是一个HTML标签。许多正则表达式的新手会首先想到用正则表达式 < < <.+> > > ,他们会很惊讶的发现,对于测试字符串,“Thisisa first test”,你可能期望会返回 ,然后继续进行匹配的时候,返回 。但事实是不会。VB.NET正则表达式匹配“ first ”。很显然这不是我们想要的结果。原因在于“+”是贪婪的。也就是说,“+”会导致正则表达式引擎试图尽可能的重复前导字符。只有当这种重复会引起整个VB.NET正则表达式匹配失败的情况下,引擎会进行回溯。也就是说,它会放弃***一次的“重复”,然后处理正则表达式余下的部分。和“+”类似,“?*”的重复也是贪婪的。
深入正则表达式引擎内部让我们来看看正则引擎如何匹配前面的例子。***个记号是“ <”,这是一个文字符号。第二个符号是“.”,匹配了字符“E”,然后“+”一直可以匹配其余的字符,直到一行的结束。然后到了换行符,匹配失败(“.”不匹配换行符)。于是引擎开始对下一个正则表达式符号进行匹配。也即试图匹配“> ”。到目前为止,“ <.+”已经匹配了“ first test”。引擎会试图将“> ”与换行符进行匹配,结果失败了。于是引擎进行回溯。结果是现在“ <.+”匹配“ first tes”。于是引擎将“> ”与“t”进行匹配。显然还是会失败。这个过程继续,直到“ <.+”匹配“ first ”与“> ”匹配。于是引擎找到了一个匹配“ first ”。记住,正则导向的引擎是“急切的”,所以它会急着报告它找到的***个匹配。而不是继续回溯,即使可能会有更好的匹配,例如“ ”。所以我们可以看到,由于“+”的贪婪性,使得正则表达式引擎返回了一个最左边的最长的匹配。
用懒惰性取代贪婪性一个用于修正以上问题的可能方案是用“+”的惰性代替贪婪性。你可以在“+”后面紧跟一个问号“?”来达到这一点。“*”,“{}”和“?”表示的重复也可以用这个方案。因此在上面的例子中我们可以使用“ <.+?> ”。让我们再来看看正则表达式引擎的处理过程。再一次,正则表达式记号“ <”会匹配字符串的***个“ <”。下一个正则记号是“.”。这次是一个懒惰的“+”来重复上一个字符。这告诉正则引擎,尽可能少的重复上一个字符。因此引擎匹配“.”和字符“E”,然后用“> ”匹配“M”,结果失败了。引擎会进行回溯,和上一个例子不同,因为是惰性重复,所以引擎是扩展惰性重复而不是减少,于是“ <.+”现在被扩展为“ ”。这次得到了一个成功匹配。引擎于是报告“ ”是一个成功的匹配。整个过程大致如此。
惰性扩展的一个替代方案我们还有一个更好的替代方案。可以用一个贪婪重复与一个取反字符集:“ <[^> ]+> ”。之所以说这是一个更好的方案在于使用惰性重复时,引擎会在找到一个成功匹配前对每一个字符进行回溯。而使用取反字符集则不需要进行回溯。***要记住的是,本教程仅仅谈到的是正则导向的引擎。文本导向的引擎是不回溯的。但是同时他们也不支持惰性重复操作。
2.使用“.”匹配几乎任意字符
在正则表达式中,“.”是最常用的符号之一。不幸的是,它也是最容易被误用的符号之。“.”匹配一个单个的字符而不用关心被匹配的字符是一什么。唯一的例外是新行符。在本教程中谈到的引擎,缺省情况下都是不匹配新行符的。因此在缺省情况下,“.”等于是字符集[^\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”。你想要你的正则表达式达到如何***的程度取决于你想达到什么样的目的。如果你想校验用户输入,则需要尽可能的***。如果你只是想分析一个已知的源,并且我们知道没有错误的数据,用一个比较好的VB.NET正则表达式匹配你想要搜寻的字符就已经足够。
3.字符串开始和结束的锚定
锚定和一般的正则表达式符号不同,它不匹配任何字符。相反,他们匹配的是字符之前或之后的位置。“^”匹配一行字符串***个字符前的位置。 < <^a> > 将会匹配字符串“abc”中的a。 < <^b> > 将不会匹配“abc”中的任何字符。类似的,$匹配字符串中***一个字符的后面的位置。所以 <
锚定的应用在编程语言中校验用户输入时,使用锚定是非常重要的。如果你想校验用户的输入为整数,用 < <^\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”。
在本文中讲述了正则表达式中的组与向后引用,先前向后查看,条件测试,单词边界,选择符等表达式及例子,并分析了正则引擎在执行匹配时的内部机理。
4.单词边界
元字符 < <\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> > ,然后 <
【编辑推荐】