全面分析Linux正则表达式(四)

运维 系统运维
在Linux正则表达式的子模式中命名子模式(named subpattern)、一次性子模式(Once-only subpatterns)是另一部分内容,现在我们加以学习。

Linux正则表达式的子模式(subpatterns),逆向引用(Back references)上篇文章应经详细介绍了,还有一部分内容介绍了量词(quantifiers),量词匹配时的greedy与ungreedy。这里加已详细介绍。

Linux正则表达式:命名子模式(named subpattern)

一些工具(例如Python)可以为逆向引用命名,从而定义出命名子模式。在Python中对正则表达式的使用是以函数或方法调用的格式,语法与这里举的例子有较大差别。有兴趣的朋友可以参看一下自己使用的工具来看看是否支持命名子模式。
重复(Repetition)和量词(quantifiers)
在前面介绍逆向引用的部分里我们已经接触到了量词(quantifiers)的概念,例如前面的例子/([abc]){3}/表示三个连续的字符,每个字符都必然是 “abc”这三个字符中的一个。在这个模式里,{3}就属于量词。它表示一个模式需要重复匹配(repetition)的数目。
量词可以放在下面这些项目之后:
●单个字符(有可能是被转义的单个字符,如xhh)
●“.”元字符
● 由方括号表示的字符类
● 逆向引用
●由小括号定义的子模式(除非它是个断言,我们会在以后介绍)
最通用的量词使用形式是用花括号括起的两个由逗号分隔的数字,如这样的格式{min,max},例如,/z{2,4}/ 可以匹配 "zz", "zzz", 或者 "zzzz",花括号中的***值以及前面的逗号可以省略,例如/d{3,}/可以匹配三个以上的数字,数字的数目没有上限,而/d{3}/(注意,没有逗号)则精确的匹配3个数字。当花括号出现在不允许量词的位置或者语法与前面提到的不符时,这里它仅仅代表花括号字符本身而不再具有特殊的含义。例如{,6}不是量词,它仅仅代表这四个字符本身的含义。
为了方便,三个最常用的量词有它们的单字符缩写形式,它们的的含义如下表:
* 相当于 {0,}
+ 相当于 {1,}
? 相当于 {0,1}
这也是以上三个元字符做为量词使用含义。
在使用量词特别是没有上限限制的量词时,应该特别注意不要构成无限循环,例如/(a?)*/,在有的正则表达式工具里。这会形成一个编译错,不过有的工具却允许这种结构,但不能保证各种工具都可以很好的处理这种结构。
量词匹配的“greedy”与“ungreedy”
在使用带量词的模式时,我们常会发现对同一模式而言,同一个目标字符串可以有多种匹配方式。例如/d{0,1}d/,可以匹配两个或三个十进制数字,如果目标字符串是123,当量词取下限0里,它匹配“12”,当量词取上限1里,它匹配“123”整个字符。这两种匹配结果都是正确的,如果我们取它的子模式/(d{0,1}d)/,则匹配的结果1到底是“12”还是“123”?
实际的运行结果一般会是后者,因为默认情况下,大多数正则表达式工具的匹配是按“greedy”原则匹配的。“greedy”单词的中的含义是“贪吃的, 贪婪的”的意思,它的行为也如此单词的含义,所谓greedy匹配意指在量词限制范围内,只要能保持后续模式的匹配,匹配总是尽可能的重复下去,直到不匹配的情况发生为止。为便于理解,我们看下面这个简单的例子。
/(d{1,5})d/匹配“12345”这个字符串,这个模式表示在1到5个数字后面跟上一个数字,量词范围从1到5,当它的值在1-4时,整个模式都是匹配的,1的值可以是“1”,“12”,“123”,“1234”,而在greedy匹配的情况下,它取匹配时的量词***值,因此最终匹配的结果是”1234”。
在大多数情况下,这就是我们想要的结果,但情况并不总这样。例如,我们希望用下面这个模式提取出c语言的注释部分(在c语言中,注释语句放在字符串/*和*/之间)。我们使用的正则表达式是/*.**/,但匹配的结果却完全和需要的不同。当正则表达式解析到“/*”这后的“.*”时,因为“.”可以代表任意字符,这也包含了其后需要匹配的“*/”,在量词的作用下,这个匹配将一直进行下去,超过下一个“*”/直到文本的结束,这显然不是我们需要的结果。
为了完成如上例我们想要的那种匹配,正则表达式引入了ungreedy匹配方法,与greedy匹配相反,在满足整个模式匹配的前提下,它总是取最小的量词数目结果。Ungreedy匹配用在量词后面加上问号“?”来表示。例如在匹配C语言的注释时,我们把正则表达式写成如下形式:/*.*?*/,在量词“*”后加上问号就可以达成想要的结果。还有前面那个例子用/(d{1,5})d/匹配“12345”这个字符串,如果改写为ungreedy模式向这样/(d{1,5}?)d/,、1的值将为1。
上面的解释也许有些不准确,量词后的问号的作用实际上是反转当前的正则表达式的greedy与ungreedy行为。你可以通过模式修正符“U”将正则表达式设成ungreedy模式然后在模式中通过量词后的问号将之反转为greedy。

Linux正则表达式:一次性子模式(Once-only subpatterns)

关于量词的另一个有趣的话题是一次性子模式(Once-only subpatterns)。要理解它的概念需要先了解一下含有量词的正则表达式的匹配过程。我们这里举个例子。
现在,让我们用模式/d+foo/来匹配字符串“123456bar”,当然,它的结果是没有匹配。但正则表达式引擎是如何工作的呢?它先分析前面的d+,这代表一个以上的数字,然后检查目标字符串的对应位置的***个字符“1”,符合模式,然后根据量词重复这个模式对字符串进行匹配直到“123456”始终符合“d+”模式,接着它在目标字符串中遇到字符“b”无法与“d+”匹配,于是查看“d+”的后续模式“foo”,与目标字符串的后续部分“bar”无法匹配,这时,有趣的事情出现了,解释引擎会对前面已经解析过的“d+”模式进行回溯,将量词数目减少一,看剩余部分能否匹配,此时“d+”的值改为“12345”,然后解释引擎看目标字符串剩余的部分“6bar”能否与剩余的模式“foo”相匹配,如果不行,就把量词数再减一,直到达到最小的量词限制,如果仍无法匹配,则表明目标字符串无法匹配,返回无法匹配的结果。
现在,我们就可以来接触一次性子模式了。所谓一次性子模式就是定义在正则表达式解析时不需要上述回溯过程的子模式。它用左圆括号后面的问号和小于号来表示,向这样(?>)。如果将上面提到的例子改为一次性子模式,可以这样书写:
/(?>d)+foo/,这时,当解析器遇到后面不匹配的bar时,会立即返回不匹配的结果,而不会进行前面提到的回溯过程。
需要了解的是,一次性子模式属于非捕获子模式,它的匹配结果不能被逆向引用。
当一个没有设定重复上限的子模式中包含了同样没有设定重复上限的模式时,使用一次性子模式是唯一可以避免让你的程序陷入长时间等待的方法。例如你用“/(D+|<d+>)*[!?]/”这个模式去匹配一长串的a字符,向这样“aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa”,在返回最终无匹配的结果前,你会等待很长的一段时间。这个模式表示一串非数字字符或者用尖括号括着的一串数字后跟随着叹号或者问号,把这段字符串分成两个重复的部分会有很多种分法,而无论是子模式本身还是子模式之内的量词的各可能值都要经过逐一测试,这将使最终的运算量达到一个很大的程度。这样,你将在电脑前等待相当长的时间才会看到结果。而如果用一次性子模式来改写刚才的模式,改成这样/ ((?>D+)|<d+>)*[!?]/,你就可以很快得到运算的结果。

 

【编辑推荐】

  1. 全面分析Linux正则表达式(一)
  2. 详细介绍Linux虚拟机的安装方法
  3. 详细剖析Linux服务器安全防护十个方面
  4. 分享一个简单的Linux数据备份方案
  5. 怎样处理Linux死机
责任编辑:小霞 来源: 中国网管联盟
相关推荐

2010-03-03 10:51:32

正则表达式

2010-03-03 13:31:25

Linux正则表达式

2010-03-03 11:03:51

Linux正则表达式

2010-03-03 13:09:10

Linux正则表达式

2010-03-03 12:53:50

Linux正则表达式

2022-01-04 11:35:03

Linux Shel正则表达式Linux

2017-05-12 10:47:45

Linux正则表达式程序基础

2010-07-14 09:47:04

Perl正则表达式

2010-07-13 16:56:30

Perl正则表达式

2018-09-27 15:25:08

正则表达式前端

2020-09-04 09:16:04

Python正则表达式虚拟机

2024-09-14 09:18:14

Python正则表达式

2009-03-24 08:56:15

正则表达式格式清理字符串

2010-03-25 18:25:36

Python正则表达式

2023-09-13 08:12:45

2009-09-16 17:15:57

正则表达式引擎

2016-11-10 16:21:22

Java 正则表达式

2011-06-02 12:34:16

正则表达式

2022-03-28 06:19:14

正则表达式开发

2019-07-17 15:45:47

正则表达式字符串前端
点赞
收藏

51CTO技术栈公众号