深入探索正则表达式(第二部分)

更新:11-13 名人轶事 我要投稿 纠错 投诉

单词边界

元字符b 是位置匹配的“锚点”。该匹配是0 长度匹配。

有4 个位置被视为“字边界”:

如果字符串的第一个字符是“单词字符”,则为字符串的第一个字符之前的位置如果字符串的最后一个字符是“单词字符”,则为字符串的最后一个字符之后的位置当为“非”时-单词字符”紧跟在“单词字符”之后,位于“单词字符”和“非单词字符”之间当“单词字符”紧跟在“非单词字符”之后,位于“非单词字符”之间字符”和“非单词字符” 在“单词字符”之间与“w”匹配的字符

“非单词字符” 与“W”匹配的字符

在大多数正则表达式实现中,“单词字符”通常包括[a-zA-Z0-9_]

例子

正则表达式b4b

匹配单个4,而不是较大数字的一部分(不匹配44 中的4)

也就是说,几乎可以说b 匹配“字母数字序列”的开头和结尾。

“字边界”的否定集是B

他要匹配的位置是两个“单词字符”之间或者两个“非单词字符”之间的位置。

深入正则表达式引擎内部

示例

正则表达式bisb

字符串这个岛很美丽

引擎首先处理符号b

因为b 的长度为0,所以检查第一个字符T 之前的位置。

T是一个“单词字符”,它前面的字符是空字符(void)。这是单词边界,b 匹配成功。

但是正则表达式中的i和第一个字符T匹配失败,回溯~

单词边界b 继续匹配。第五个空格字符和第四个字符s 之间有一个单词边界。 b 匹配成功。

但是i和正则表达式中第五个空格字符匹配失败,回溯~

单词边界b 继续匹配。第五个空格字符和第六个字符i 之间存在单词边界。 b 匹配成功。

正则表达式成功匹配第6 个和第7 个字符。

但第8个字符l与单词边界b不匹配,匹配失败,回溯~

单词边界b继续匹配,第13个字符i和前面的空格字符形成“单词边界”,并且is和is同时匹配。正则表达式中的第二个b开始匹配,

单词s 及其后面的空格字符是单词边界,b 匹配成功。

正则表达式结束。

引擎“急忙”返回匹配成功的结果。

选择符

“|”正则表达式中表示选择。您可以使用选择器来匹配多个可能的正则表达式之一。

如果你想匹配文本“cat”或“dog”

正则表达式猫|狗

如果你想要更多匹配,只需添加cat|dog|mouse|fish

选择器在正则表达式中具有最低优先级,这意味着它告诉引擎匹配选择器左侧的所有表达式或右侧的所有表达式。

您还可以使用括号来限制选择器的范围。

例子

b(cat|dog)b 告诉正则引擎将(cat|dog) 视为正则表达式单元。

正则表达式引擎是急切的:当它找到有效的匹配时,它会停止搜索。

因此,在某些条件下,选择器两侧表达式的顺序都会影响结果。

例子

使用正则表达式搜索编程语言的函数列表

Get 或GetValue 或Set 或SetValue

一个明显的解决方案是正则表达式Get|GetValue|Set|SetValue

结果

因为正则表达式Get和GetValue都匹配失败,而Set匹配成功。因为面向正则的引擎是“渴望的”,所以它会返回第一个成功的匹配,即文本Set,而不会继续搜索其他更好的匹配。

与我们的预期相反,正则表达式没有匹配整个字符串。有几种可能的解决方案。

1.改变选项的顺序,例如我们使用正则表达式GetValue|Get|SetValue|Set,这样我们就可以先搜索最长的匹配。

2. 将四个选项合并为两个选项Get(Value)?|Set(Value)?

因为问号重复器是贪婪的,所以SetValue总是会先于Set进行匹配。

3.更好的解决方案是使用字边界

b(Get|GetValue|Set|SetValue)b 或b(Get(值)?|Set(值)?b

由于所有选择都有相同的结尾,因此正则表达式可以优化为b(Get|Set)(Value)?b

组与向后引用

您可以通过将正则表达式的各个部分放在括号内来对它们进行分组。

然后,您可以对整个组使用一些常规操作,例如重复运算符。

注意差异

() 括号用于构成正则表达式组

[]用于定义字符集

{}用于定义重复操作

当使用()定义正则表达式组时,正则表达式引擎会对匹配到的组按顺序编号,并将其存储在缓存中。

向后引用匹配组时,可以使用“number”来引用它。

正则表达式1 指第一个匹配的反向引用组,2 指第二组,依此类推,n 指第n 个组。

指的是整个正则表达式本身。

假设您要匹配HTML 标记的开始标记和结束标记以及标记中间的文本。

例子

匹配and 和之间的文本。

文本这是一个测试

正则表达式([A-Z][A-Z0-9]*)[^]*.*?1

首先,正则表达式将匹配第一个文本字符

然后,正则表达式[A-Z]匹配文本B

[A-Z0-9]* 匹配0 到多个字母数字字符,后跟0 到多个非“”字符。

最后,正则表达式将匹配文本

接下来,正则引擎会对结束标记之前的字符进行惰性匹配(急于炫耀,为了找到最短的文本,每次.*? 匹配成功时,它都会尝试匹配正则表达式。

那么正则表达式中的“1”代表对先前匹配的组([A-Z][A-Z0-9]*)的引用。本例中引用的标签名称为文本字符B,因此需要匹配的结束标签为

同一个反向引用组可以被多次引用

正则表达式([a-c])x1x1

将匹配文本

亚克萨

xbxb

CXC

如果对组的数字引用没有有效匹配,则该引用就是空的。

反向引用不能单独使用。

错误的正则表达式([abc]1)

不能用于正则表达式匹配本身,它只能用于替换操作。

不能在字符集中使用反向引用。

在[] 括起来的字符集中,1 被解释为转义的八进制形式。

所以正则表达式(a)[1b]中的1并不代表反向引用。

反向引用会减慢引擎速度,因为它需要存储匹配组。

如果不需要反向引用,可以告诉引擎不要存储某个组。例如Get(?Value)

(后面跟着?)将告诉引擎不要存储组(Value)的匹配值以供向后引用。

重复操作与后向引用

对组使用重复运算符时,缓存中的向后引用内容会不断刷新,只保留最后一次匹配的内容。

例子

正则表达式([abc]+)=1

可以匹配文本cab=cab

但正则表达式([abc])+=1 不会匹配文本cab=cab

因为

([abc]) 第一次匹配文本c时,1已经代表c

([abc]) 继续匹配已经代表a 的文本a1

([abc]) 继续匹配文本b。最后的1已经代表b。

所以正则表达式([abc])+=1 只会匹配文本cab=b

应用程序: 检查重复单词

编辑文本时,很容易键入重复的单词,例如

这些重复的单词可以使用b(w+)s+1b 来检测。

要删除第二个单词,只需使用替换函数替换“1”即可

组的命名和引用

在PHP 和Python 中,您可以使用(?Pgroup) 来命名组。

在本例中,P 是命名组名。

可以通过(?P=name) 引用

.NET 的命名组

.NET框架还支持命名组。不幸的是,微软程序员决定发明自己的语法,而不是遵循Perl 和Python 的规则。迄今为止,还没有其他正则表达式实现支持Microsoft 发明的语法。

.NET 示例

(?组)(?"第二"组)

如您所见,NET 提供了两种词法方法来创建命名组:

在字符串中使用尖括号更方便

使用单引号。在ASP 代码中更有用,因为它们在ASP 代码中用作HTML 标记。

引用命名组

k 或k"名称"

执行搜索和替换时,使用${name} 引用命名组。

正则表达式的匹配模式

正则表达式引擎支持三种匹配模式

/i 使正则表达式不区分大小写

/s 打开“单行模式”,即点。匹配换行符(nweline)

/m 打开“多行模式”,即^ 和$ 匹配换行符(nweline)前后的位置。

在正则表达式中打开或关闭模式

如果在正则表达式中插入修饰符(?ism)

那么修饰符只作用于其右边的正则表达式。 (?-i) 关闭不区分大小写。您可以快速测试一下。

(?i)te(?-i)st 应匹配TEst,但不匹配teST 或TEST

原子组与防止回溯

在某些特殊情况下,回溯会使引擎效率极低。

例子

要匹配这样的字符串,请在字符串中的每个字段之间使用逗号作为分隔符,第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不匹配,匹配失败,继续回溯。

.这样的回溯组合是一个非常大的数字,可能会导致引擎崩溃。

有一个解决方案: 可以防止如此巨大的回溯。

1.简单的解决方案,使匹配尽可能准确

使用否定字符集而不是句点。例如,我们使用以下正则表达式^([^,rn]*,){11}P

这使得失败的回溯次数减少到11 次。

2.使用原子团

原子组的目的是使正则表达式引擎更快地失败。因此,可以有效防止大规模回溯。原子组的语法是(?正则表达式)

(?) 之间的所有正则表达式都被视为单个正则表达式。一旦匹配失败,引擎就会回溯到正则表达式中原子组之前的部分。前面使用原子团的示例可以表示为^(?(.*?){11})P。一旦第12个字段匹配失败,引擎就会回溯到原子组前面的^。

向前查看与向后查看

Perl 5 引入了两种强大的正则语法:“向前看”和“向后看”

它们也称为“零长度断言”。与锚点一样,它们的长度为零(即正则表达式不消耗匹配的字符串)

不同之处在于“look before and after”实际上会匹配字符,只是它们会丢弃匹配并仅返回:个匹配或没有匹配的匹配结果。这就是为什么它们被称为“断言”。它们实际上并不消耗字符串中的字符,而只是断言是否可能匹配。

注意:Javascript仅支持向前查看,不支持向后查看。

肯定和否定式的向前查看

上一个示例

找到后面不紧跟着u 的q

即要么q后面没有字符,要么后面的字符不是u

使用负前瞻的解决方案是q(?u)

负向前看的语法是(? 看什么)

正向前瞻和负向前瞻非常相似:=查看内容)

如果“查看的内容”部分中有组,也会生成反向引用。但向前查找本身并不会产生向后引用,也不会计入向后引用的数量。这是因为前向查找本身会被丢弃,只保留匹配与否的结果。如果您想保留匹配结果作为反向引用,可以使用(?=(regex)) 生成反向引用。

肯定和否定式的先后查看

向后看与向前看具有相同的效果,但方向相反。否定向后看的语法为:(? 肯定向后看的语法为:(?=查看内容)。我们可以看到,与向前看相比,多了一个左尖括号表示方向。示例:( ? 将匹配没有“a”作为前导字符的“b” 值得注意的是,从当前字符串位置开始向后查找;

Look会从当前字符串位置回溯一个字符,然后开始匹配“View”正则表达式。

深入正则表达式引擎内部

简单示例

将正则表达式q(?u) 应用于字符串伊拉克

正则表达式的第一个符号是q

开始匹配。当第四个字符q匹配时,q后面跟着一个空字符(void)。

下一个常规符号是向前看。引擎注意到已输入前瞻正则表达式部分。下一个正则符号u与空字符不匹配,导致正向查找中的正则表达式匹配失败。因为是消极的前瞻性,所以意味着整个前瞻性是成功的。所以返回匹配结果q。

我们将相同的正则表达式应用于文本quit

正则表达式q 匹配q。下一个正则符号是正则表达式u 的前瞻部分。

它匹配字符串中的第二个字符i,引擎继续处理下一个字符i

引擎现在注意到先行部分已被处理,并且先行已成功。然后引擎会丢弃字符串的匹配部分,这将导致引擎回退到字符u

因为lookahead为负,这意味着lookahead部分的成功匹配会导致整个lookahead失败,因此引擎必须回溯。最后,因为没有其他文本q与正则表达式q相匹配,所以整个匹配失败。

为了确保您清楚地理解前瞻的实现,让我们将正则表达式q(?=u)i 应用于文本quit

正则表达式q 首先匹配q

然后向前看,成功匹配u的部分被丢弃,只返回匹配的判断结果。引擎从字符i 回退到字符u

由于前瞻成功,引擎继续处理下一个常规符号。结果发现它与“u”不匹配。因此比赛失败了。由于其后没有其他“q”,因此整个正则表达式匹配失败。

详细了解正则表达式引擎的内部机制

让我们将(?=a)b 应用于“thingamabob”。引擎开始处理常规符号的后视部分和字符串中的第一个字符。在此示例中,向后查找告诉正则表达式引擎返回一个字符并查看“a”是否匹配。因为“t”之前没有字符,所以引擎无法回退。所以向后看是失败的。引擎继续处理下一个字符“h”。同样,引擎暂时后退一个字符并检查“a”是否匹配。结果发现了一个“t”。回头看又失败了。

后向查找继续失败,直到正则表达式到达字符串中的“m”,并且匹配正向后向查找。因为长度为零,所以字符串的当前位置仍然是“m”。下一个常规符号是,它无法匹配“m”。下一个字符是字符串中的第二个“a”。引擎暂时后退一个字符并发现“m”不匹配。

下一个字符是字符串中的第一个“b”。引擎暂时回退一个字符,发现回溯满足并且“b”匹配。这样整个正则表达式就被匹配了。结果,正则表达式返回字符串中的第一个“b”。

向前和向后的应用

让我们看一下示例:查找包含“cat”的6 个字符单词。首先,我们可以不向前向后解决问题,例如:

catw{3}|wcatw{2}|w{2}catw|w{3}cat 很简单!但是当要求变成查找6-12个字符,包含“猫”,“狗”或“老鼠”的字符时

当使用单词时,这种方法会变得有点笨拙。

让我们看一下使用前瞻的场景。在这个例子中,我们有两个基本需求来满足:一是我们

要求是6位字符,第二是单词中含有“cat”。满足第一个要求的正则表达式是bw{6}b。满足第二个要求的正则表达式是

bwcatwb。

将两者结合起来,我们可以得到下面的正则表达式:

(?=bw{6}b)bwcatwb

具体的匹配过程留给读者去思考。但需要注意的一点是,向前查找并不消耗字符,因此当判断出的单词满足有6个字符的条件时,引擎会从开始判断之前的位置继续匹配后续的正则表达式。

最后经过一番优化,可以得到如下正则表达式:

b(?=w{6}b)w{0,3}猫w*

关于深入探索正则表达式(第二部分)的内容到此结束,希望对大家有所帮助。

用户评论

烟花巷陌

我又开始学习正则表达式了!这次看第二篇内容,希望能把之前学的巩固一下。

    有20位网友表示赞同!

墨染殇雪

感觉正则表达式的强大还在后面啊,要好好掌握下它。

    有5位网友表示赞同!

白恍

希望这篇文章能讲一些更先进的用法,比如嵌套或者分组匹配的技巧。

    有8位网友表示赞同!

墨城烟柳

学习代码越来越依赖正则表达式了!

    有6位网友表示赞同!

黑夜漫长

以前就觉得这个东西很神奇,现在终于要认真学习一遍了。

    有18位网友表示赞同!

一点一点把你清空

文章内容简单易懂吗?我有些基础但不太熟悉高级用法啊。

    有16位网友表示赞同!

念初

最近在写脚本经常用到正则,希望能学到一些新的技巧。

    有11位网友表示赞同!

■孤独像过不去的桥≈

正则表达式总是觉得很复杂,希望这篇文章能帮我把它理清思路。

    有15位网友表示赞同!

执妄

要学习好正则表达式似乎需要很多练习,哪有好的练习资源推荐?

    有14位网友表示赞同!

清羽墨安

看完第一篇还没消化完,就来期待第二篇的内容了!

    有12位网友表示赞同!

寂莫

正则表达式真让人头疼,希望这篇文章能给我一些启发。

    有5位网友表示赞同!

男神大妈

我一直在尝试用正则表达式解决一些问题,但是总觉得效果不理想。

    有10位网友表示赞同!

浮光浅夏ζ

想把之前学的正则知识巩固一下,再学习一些新的应用场景。

    有12位网友表示赞同!

爱情的过失

这篇文章讲的不都是基础用法吧?希望有更高级的技巧分享。

    有7位网友表示赞同!

半梦半醒半疯癫

看完正则表达式讲解感觉很有意思,想尝试自己写一些表达式试试看。

    有11位网友表示赞同!

轨迹!

希望能看到更多例子和实践应用,这样更容易理解了。

    有8位网友表示赞同!

←极§速

准备参加一场技术面试,需要提前好好review正则表达式的知识点。

    有14位网友表示赞同!

苏樱凉

学习正则表达式很有用,可以提高编程效率很多!

    有19位网友表示赞同!

【深入探索正则表达式(第二部分)】相关文章:

1.蛤蟆讨媳妇【哈尼族民间故事】

2.米颠拜石

3.王羲之临池学书

4.清代敢于创新的“浓墨宰相”——刘墉

5.“巧取豪夺”的由来--米芾逸事

6.荒唐洁癖 惜砚如身(米芾逸事)

7.拜石为兄--米芾逸事

8.郑板桥轶事十则

9.王献之被公主抢亲后的悲惨人生

10.史上真实张三丰:在棺材中竟神奇复活

上一篇:深入理解函数参数:变量、指针与引用差异解析 下一篇:重温张老师经典语录,传承智慧与魅力