匹配,如果没有\K
,这3个字母将放进$&
中,但是\K
使得匹配完abc后立即切断前面的匹配,也就是从c字母后面开始重新匹配,所以这里匹配的结果是22ABC。
再例如,"abc123abcfoo"=~ /(abc)123\K\g1foo/;
,它匹配到123后被切断,但是分组引用还可以继续引用,所以匹配的结果是"abcfoo"。
在基础正则中,那些能匹配多次的量词都会匹配最长内容。这种尽量多匹配的行为称为"贪婪匹配"(greedy match)。
例如字符串"aa1122ccbb",用正则表达式a.*c
去匹配这个字符串,其中的.*
将直接从第二个字母a开始匹配到最结尾的b,因为从第二个字母a开始到最后一个字母b都符合.*
的匹配模式。再然后,去匹配字母c,但因为已经把所有字母匹配完了,只能回退一个字母一个字母地释放,每释放一个就匹配一次字母c,发现回退释放到倒数第三个字母就能满足匹配要求,于是这里的.*
最终匹配的内容是"a1122c"。
上面涉及到回溯的概念,也就是将那些已经被量词匹配的内容回退释放。
上面描述的是贪婪匹配行为,还有非贪婪匹配、占有优先匹配,以下简单描述下他们的意义:
有必要搞清楚这几种匹配模式在匹配机制上的区别:
除了上面描述的*
量词会进行贪婪匹配,其他所有能进行多次匹配的量词可以选择贪婪匹配模式、非贪婪匹配模式和占有优先匹配模式,只需选择对应的量词元字符即可。如下:
几点需要说明:
看以下示例即可理解贪婪和非贪婪匹配的行为:
以下是占有优先匹配模式的示例:
所以,在使用占有优先匹配模式时,它后面不应该跟其他表达式,例如a*+x
永远匹配不了东西。绝大多数时候都是不会回溯的。但是少数情况下,它并非强制锁住回溯,这个和正则引擎匹配原理有关,本文不多做解释。
另外,固化分组和占有优先并不完全等价,它们只是匹配行为相同:匹配后不回溯。具体可对比后文对应内容。
在基础正则中,使用括号可以对匹配的内容进行分组,这种行为称为分组捕获。捕获后可以通过\1
这种反向引用方式去引用(访问)保存在分组中的匹配结果。
例如:
在perl中,还可以使用\gN
的方式来反向引用分组,这个在上一节"反斜线序列"中已经解释过了。例如,以下是和上面等价的几种写法:
perl还会把分组的内容放进perl自带的特殊变量$1,$2,...,$N
中,它们和\1,\2,...\N
在匹配成功时的结果上没有区别,但是\N
这种类型的反向引用只在正则匹配中有效,正则匹配结束后就消亡了,而$N
因为是perl的变量,即使正则已经退出匹配,也依然可以引用。所以,我们可以使用$N
的方式来输出分组匹配的结果:
有两点需要注意:
第一点,示例可知:
第二点,从机制上去分析。\1
是每个正则匹配都相互独立的,而$1
则保存分组捕获成功的值,即使这次值是上次捕获的。
这里稍微解释下正则匹配关于分组捕获的匹配过程:
例如,匹配表达式"12abc22abc" =~ /\d(abc)\d\d\1/;
,当正则引擎去匹配数据时:
1.首先匹配第一个数字1,发现符合\d
,于是继续用(abc)
去匹配字符串,因为发现了是分组括号,于是会将第二个字符2放进分组,发现不匹配字母a,于是匹配失败,丢弃这个分组中的内容。
2.正则引擎继续向后匹配数值2,发现符合\d
,于是用(abc)
去匹配字符串,接着会将第三个字符a放进分组,发现能匹配,继续匹配字符串中的b、c发现都能匹配,于是分组捕获完成,将其赋值给$1
,之后就能用\1
和$1
去引用这个分组的内容。
3.后面继续去匹配\d\d\1
,直到匹配结束。
当然,具体匹配的过程不会真的这么简单,它会有一些优化匹配方式,以上只是用逻辑去描述匹配的过程。
在perl中,支持的分组捕获更强大、更完整,它除了支持普通分组(也就是直接用括号的分组),还支持:
匿名捕获是指仅分组,不捕获。因为不捕获,所以无法使用反向引用,也不会将分组结果赋值给$1
这种特殊变量。
虽然有了分组捕获功能,就可以实现任何需求,但有时候可以让这种行为变得更人性化,减少维护力度。
例如字符串"xiaofang or longshuai",使用模式/(\w+) or (\w+)/
去捕获,用$1
和$2
分别引用or左右两个单词:
但如果需求是中间的关系or也可以换成and,为了同时满足and和or两种需求,使用模式/(\w+) (and|or) (\w+)/
去匹配,但是这时引用的序号就得由$2
变为$3
:
如果使用匿名捕获,对and和or这样无关紧要,却有可能改变匹配行为的内容,可以将其放进一个无关的分组中。这样不会对原有的其余正则表达式产生任何影响:
注意上面仍然使用$2
引用第三个括号。
同样,如果要在正则内部使用反向引用,也一样使用\2
来引用第三个括号。
另外,在前文还介绍过一个n
修饰符,它也表示非捕获仅分组行为。但它只对普通分组有效,对命名分组无效。且因为它是修饰符,它会使所有的普通分组都变成非捕获模式。
由于上面开启了n修饰符,使得3个普通分组括号都变成非捕获仅分组行为,所以\1
和$1
都无法使用。除非正则中使用了命名分组。
命名捕获是指将捕获到的内容放进分组,这个分组是有名称的分组,所以后面可以使用分组名去引用已捕获进这个分组的内容。除此之外,和普通分组并无区别。
当要进行命名捕获时,使用(?<NAME>)
的方式替代以前的分组括号()
即可。例如,要匹配abc并将其分组,以前普通分组的方式是(abc)
,如果将其放进命名为name1的分组中:(?<name1>abc)
。
当使用命名捕获的时候,要在正则内部引用这个命名捕获,除了可以使用序号类的绝对引用(如\1
或\g1
或\g{1}
),还可以使用以下任意一种按名称引用方式:
如果要在正则外部引用这个命名捕获,除了可以使用序号类的绝对应用(如$1
),还可以使用$+{NAME}
的方式。
实际上,后一种引用方式的本质是perl将命名捕获的内容放进了一个名为%+
的特殊hash类型中,所以可以使用$+{NAME}
的方式引用,如果你不知道这一点,那就无视与此相关的内容即可,不过都很简单,一看就懂。
例如:
其中上述代码中的\g1
还可以替换为\1
、\g{firstname}
、\k{firstname}
或\k<firstname>
。
通过使用命名捕获,可以无视序号,直接使用名称即可准确引用。
首先固化分组不是一种分组,所以无法去引用它。它和"占有优先"匹配模式(贪婪匹配、惰性匹配、占有优先匹配三种匹配模式,见后文)是等价的除了这两种称呼,在不同的书、不同的语言里还有一种称呼:原子匹配。
它的表示形式类似于分组(?>)
,所以有些地方将其称呼为"固化分组"。再次说明,固化分组不是分组,无法进行引用。如果非要将其看作是分组,可以将其理解为被限定的匿名分组:不捕获,只分组。
如果不知道什么是回溯,看完下面的例子就明白。
例如"hello world"可以被hel.* world
成功匹配,但不能被hel(?>.*) world
匹配。因为正常情况下,.*
匹配到所有内容,然后往回释放已匹配的内容直到释放完空格