1.1 介绍
这个系列的文章是为了向你介绍正则表达式引擎的工作原理,懂得这些原理是写出高效的正则表达式的关键。不仅如此这还将帮助你避免很多常见的错误,减少你花在猜测正则表达式的行为的时间。
2.1. 字面量字符(Literal Characters)
最基础的正则表达式由一个字面量字符组成,例如a
。它将匹配一个字符串中第一个a
出现的位置。如果它匹配的是 Jack is a boy
,那么它将匹配到J后面的a。事实上即使a出现在字符串的中间也不影响这个正则,如果你想控制a是否出现在字符串的开头或者结尾,那么你需要使用文字边界。我们会在后面的章节展开讨论。
事实上这个正则同样可以匹配字符串中的第二个a
,但是你必须通过函数调用通知正则引擎开始第二次匹配。
相类似的,表达式cat
可以匹配About cats and dogs
中的cat
。这个表达式由三个字面量字符组成,对于正则引擎而言,它的意义是:查找一个c
它的后面紧接着一个a
,a
的后面紧接着一个t
。
2.2 元字符
为了处理更复杂的正则匹配,我们需要把一些字符作为特殊字符使用。以下列出正则表达式中的12个元字符:
\
^
&
.
|
?
*
+
(
)
[
{
通常来说,这些特殊字符在单独使用时会引发错误。
如果你想把以上的字符作为字面量字符来使用,你必须使用\
进行转义。例如,如果你想匹配1+1=2
,那么正确的表达式为/1\+1=2/
,因为加号有特殊的含义。
如果你忘记对元字符进行转义,那么在有些情况下会导致正则表达式非法,例如/+1/
。这时程序会抛出异常。
2.2.1 {
的转义
在大多数正则引擎中,我们可以直接把{
作为字面量字符使用,除了作为重复操作使用例如a{1,3}
。所以在一般情况下我们不需要对{
进行转义,当然转义也不会报错。在一些特殊的正则引擎中,我们可能需要对它进行转义,例如在java中,}
需要转义,在Boost和std::regex中{
、}
都需要转义。
2.2.2 ]
的转义
]
在字符类以外的情况下使用时,它是一个字面量字符。在字符类中使用的时候,则有不同的规则。具体的规则我们会在字符类专题的展开讨论。
2.2.3 其它字符
除了以上这些元字符以外,其它的字符都不需要使用反斜杠转义。这是因为转移符和字面量字符的组合将成为一个正则指令(regex token),它具有特殊的含义。例如\d
可以匹配0~9中任意一个字符。
2.3. 特殊字符与编程语言
在正则表达式中'
和"
并不是特殊字符,如何你是一个程序员的话你一定对此十分意外。在你编程或是使用文本编辑器的高级搜索功能时,你不需要对单引号和双引号进行转义。
当你在代码中使用正则表达式的时候,你应当注意有些字符在你所使用的编程语言中有特殊的含义。这是因为这些字符在进入正则引擎之前,先要经过编译器的处理。在c++代码中,表达式1\+1=2
需要写成1\\+1=2
,c++编译器会在编译过程中去掉一个反斜杠,并把编译后的结果传递给正则引擎。再举一个例子,正则表达式c:\\temp
可以匹配字符串c:\temp
,但是在c++中你需要这么写c:\\\\temp
。简单来说就是用四个反斜杠代替一个正则中的斜杠。
有关正则在编程语言中的使用,我们对在后面的章节中展开讨论。
3.1 行终结符(Line Breaks)
你可以在正则表达式的特殊字符匹配非打印字符。
\t
:匹配制表符\r
:匹配回车\n
:匹配换行\a
:匹配响铃\e
:匹配esc键\f
:匹配分页符
\R
是一个特殊的转义符,它将匹配所有的行终结符,也包括Unicode行终结符。相对于\r
或者\n
,\R
比较特殊的一点是它会把[CRLF对]作为一个整体去匹配,而不会匹配把CR和LF分开匹配(作者译:如果他们同时出现的话)。当\R
在一个CRLF对之前出现的话,单个\R
将匹配整个CRLF对。在对一个CRLF进行匹配的时候,\R
不会向前回溯从而匹配到CR。\R
可以匹配到单独的CR或是单独的LF,但是\R{2}
或者\R\R
不会匹配到一个CRLF对,因为第一\R
已经匹配到了整个CRLF对,另一个\R
就不能和任何字符匹配。
但是在一些语言中,\R
的行为并不遵循这个规范。例如在java9中\R\R
可以匹配一个CRLF对,在Perl中\R{2}
可以匹配一个CRLF对。
\R
只能向前搜索,并且匹配一个完整的CRLF对。\r\R
也可以匹配到一个CRLF对,这是因为\r
匹配到了CR,而\R
匹配了LF,这一规则在所有的引擎中都是一致的。
4.1 引擎的分类
理解正则引擎的内部原理将帮助你写出更高效的表达式,并且帮助你快速调试正则表达式中的异常。
在接下去的每一个章节中我们介绍一个新的正则特性,之后我们会解释引擎处理这个特性的详细过程。理解这些原理之后,我们便可以脱离正则可视化工具快速编写正则表达式。虽然理解引擎的原理有些难度,但是它可以帮助我们避免一些常见的错误。
在了解了这些基础知识之后,我们会介绍许多有意思的应用实例,你可以快速的把这些例子应用到你的项目中去。
虽然正则引擎有很多种不同的实现,但是大体可以分为两类:文本驱动的引擎(text-directed)和正则驱动的引擎(regex-directed)。几乎所有的现代正则引擎都采用正则驱动引擎,这是因为一些非常有用的特性只能在这种引擎上实现,例如lazy quantifiers和backreferences
4.1.1 正则驱动的引擎(regex-directed engine)
一个正则表达式引擎通过遍历正则表达式完成匹配,它尝试将表达式中的下一个token和字符串中的下一个字符进行匹配。如果当前token可以匹配成功,那么引擎将移动至下一个token,并且把这个token和字符串中的下一个字符进行匹配。如果匹配失败,那么正则引擎会在正则和字符串中进行回溯,并且重新进行路径搜索。关于正则的回溯之后的章节会详细展开。
4.1.2 文本驱动的引擎(text-directed engine)
一个文本驱动的引擎通过遍历文本完成匹配。在匹配下一个字符之前,他会尝试表达式中的所有排列。一个文本驱动的引擎没有回溯过程,所以他的匹配过程相对简单。在大多数情况下两种引擎的匹配结果是相同的。
本教程主要讨论正则驱动的引擎,所以默认情况下我们提到的引擎都是正则驱动引擎,除非两种引擎的匹配结果不一致。只有当我们使用选择符,并且两个选项匹配到同一个位置时才会发生这种情况。
4.2 正则表达式总是匹配最左端的匹配结果
正则表达式总是匹配最左端的匹配结果,无论后面是否有更好的匹配结果,这是一个非常重要的特性。当正则引擎匹配一个字符串的时候,它将从字符串的最左边开始搜索。引擎将正则中所有的排列与字符串的第一个字符相匹配。如果有一种排列匹配成功,引擎将继续匹配字符串中的下一个字符。下一步引擎将字符串中的下一个字符与正则中的所有排列进行匹配。最终引擎将返回最靠左的匹配结果。
现在我们来举一个例子。我们使用表达式 cat
去匹配字符串 He captured a catfish for his cat 。首先引擎使用 c
去匹配字符串中第一个字符 H ,此时匹配不成功而且没有其他的排列(因为c
只包含一个字面量字符)。之后引擎匹配token c
和字符 e ,同样也失败了,后面的空格也是如此。当引擎尝试匹配第四个字符的时候token c
匹配 c 成功了,所以引擎继续把第二个token a
与字符串中第5个字符 a 匹配,匹配也成功了。但是第三个token t
不能和第六个字符 p 匹配。此时引擎已经知道表达式无法和字符串中的前四个字符匹配,因此引擎将重新把第一个token c
和第5个字符 p 进行匹配,直到第15个字符时 c 才匹配成功,接下来 a
和 t
也匹配成功。
此时这个正则可以从字符串的第15个字符开始匹配成功,于是引擎非常“急切”的报告匹配成功。引擎不会继续向后搜索(即使后面会出现更好的匹配结果),因为它认为这个结果已经足够好了。
在这个例子当中两种正则引擎的搜索结果是相同的。正则的这种工作模式很大程度上决定了它的匹配结果,在之后例子中有一些匹配结果可能使你感到意外,但是只要你牢记这个搜索规则,你就可以用逻辑推导引擎的匹配结果。
5.1 字符类(字符集)
字符类(也叫字符集),它的作用是匹配一组字符中的一个字符。字符集的语法很简单,只要把字符写到方括号中间就可以了。例如[ae]
可以匹配a
或者e
。你可以用gr[ae]y
匹配gray
或者grey
。gray是美式英语,grey是英式英语。
一个字符集只能匹配一个字符。例如gr[ae]y
不能匹配graay
或者graey
。字符集中的字符排序是不分先后的,不同的顺序匹配结果是一样的。
你可以使用-
来表示一个范围。例如[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]*
。或者是一个C语言风格的十六进制数0[xX][A-Fa-f0-9]+
5.2 字符集取反操作(Negated Character Classes)
在[
后面加上一个脱字符^
可以把字符集反向使用。它的作用是匹配任何一个不属于字符集的字符。和.
符号不同,反向字符集可以匹配到不可见的行终结符,如果你不想匹配行终结符可以在字符集中加上行终结符,例如[^0-9\r\n]
它可以匹配除了换行和数字以外的任何字符。
反向字符集任然需要匹配一个字符。q[^u]
的含义并不是匹配一个后面不是u的q,它的含义是q的后面紧接着一个字符但是这个字符不是u。q[^u]
并不能匹配Iraq
中的q
,它可以匹配Iraq is a country
中的q空格
,因为空格正好能和[^u]
匹配。如果你只想匹配到q
而不想匹配到后面的空格,你可是使用negative lookahead:q(?!u)
。之后我们再讨论这个特性。
5.3 字符集中的元字符
在大多数正则引擎中,如果你在字符集中使用]
、\
、^
、-
这四个元字符,那么你需要对这些元字符进行转义。其它的元字符则不需要转义。例如你可以使用[+*]匹配加号和星号,当然你也可以对所有的元字符进行转义,这并不会导致错误,只是这么做的话会降低正则表达式的可读性。
5.3.1 反斜杠转义
如果你要在字符集中匹配一个\
,那么你需要对\
进行转义。例如[\\x]
可以匹配一个\
或者一个x
。对于]
、^
、-
来说只要他们使用的位置不会造成歧义就不需要转义。
5.3.2 脱字符转义
对于脱字符^
来说,只要他不是直接跟在[
的后面就不需要转义。例如[x^]
可以匹配一个x或者一个^
。
5.3.3 右侧方括号转义
对于]
来说,只要他是紧跟着[
或者^
它就不需要转义。例如[]x]
可以匹配]
或者x,[^]x]
可以匹配除了]
和x
以外的任何字符。
5.3.4 -
的转义
在以下情况下-
不需要转义:
- 紧接着
[
的-
,例如[-x]
]
前面的-
,例如[x-]
- 紧接着
^
的-
,例如[^-x]
、[^x-]
在其他的地方使用-
,如果不能形成一个范围的话,有可能导致一个错误,也有可能把-
作为字面量字符处理。这这一点上,各种引擎的处理方式并不统一。
如果你使用的引擎支持Unicode,你也可以在字符集中使用Unicode,例如[\u20AC]
。
待翻译:
5.4 字符集的量词匹配
如果你在字符集的后面使用量词(例如?
、*
、+
),那么你会对整个字符集进行重复,而不是仅仅重复字符集匹配到的字符。例如[0-9]+
可以同时匹配837
和222
。
如果你想对字符集的匹配结果使用量词,那么你可以使用backreferences。例如([0-9])\1+
可以匹配222但是不能匹配837。如果用它匹配833337,它会匹配到3333,如果这不是你要的结果,可以使用lookaround
5.5 字符集的原理
在这一节中我们通过一个例子来解释字符集的解说过程。我们使用gr[ae]y
去匹配Is his hair grey or gray?
,结果将匹配到grey。之前我们学习过字面量字符的匹配过程,现在我们来看一下具有多种排列方式的字符集是如何匹配的。
在匹配的过程中,前12个字符都没有匹配成功,因为它们都不能和g
匹配。直到第13个字符字符g终于和正则表达式的第一tokeng
匹配成功。下一步引擎将表达式的剩余部分和字符串匹配,这时r也匹配成功了。接下来引擎将[ae]
和字符e匹配,因为这个token是字符集,所以引擎会把字符集中的所有组合与字符串中的下一个字符e匹配。首先是用a
和字符e匹配,这一次没有匹配成功,但是此时还不能确定这个字符串的第13个字符作为起始是否能和表达式匹配,因为还有另一个排列需要尝试。接下来引擎使用字符集中的第二个tokene
,这一次匹配成功了。接下来正则引擎进行下一个tokeny
的匹配,同样也匹配成功了。
到这一步位置整个正则表达式已经匹配完成了。你可能已经注意到字符串中的gray
也可以匹配成功,但是根据正则引擎的最左原则,引擎不会继续匹配下一个可能的结果。除非你通过函数调用告诉引擎进行第二次匹配。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!