正则引擎的分类

正则引擎的分类

正则引擎的分类主要分两种:

DFA:egrep、awk、lex、flex

NFA:.NET、PHP、Perl、Ruby、Python、GNU Emacs、ed、sec、vi、grep等

NFA的历史比DFA久一点,但两种引擎都发展了20多年,产生了很多变体,POSIX的出现就是为了规范这种现象。POSIX不但规定了元字符的特性,而且规定了正则表达式应该用什么样的方式运作。

DFA符合POSIX的标准,但NFA如果要符合POSIX标准,就要作出相应的修改

所以引擎可分为:DFA、传统型NFA、POSIXNFA

测试引擎的类型

工具所采用的引擎的类型,决定了引擎能够支持的特性以及这些特性的用途和使用方式。

传统型NFA是使用最广泛的引擎,而且它很容易识别

法一:看看忽略优先量词是否得到支持,如果是,基本就可以确定是传统型NFA引擎了

法二:用nfa|nfa not来匹配nfa not,如果只有nfa匹配了,就是传统型NFA,如果整个nfa not都匹配了,要么是DFA,要么是POSIX NFA

DFA和POSIX NFA

法一:DFA不支持捕获型括号和回测

法二:用X(.+)+X来匹配形如=XX=================,如果需要花很长时间,就是NFA,如果时间很短就是DFA

Awk、lex、egrep等工具都不支持反向引用和$1功能

匹配基础 规则一

规则1:优先选择最左端的匹配结果

因为匹配先从需要查找的字符串的起始位置尝试匹配,所以起始位置最靠左的匹配结果总是优先于其他可能的匹配结果。

用cat来匹配The dragging belly indicates that your cat is too fat.匹配到的是indicates里面的cat。单词cat是能被匹配出来的,但是indicates中的cat出现得更早,所以得到的匹配是它。

如果引擎不能在字符串的开始位置能找到匹配结果,那么它就会从字符串的下一个位置开始尝试。

用fat|cat|belly|your来匹配The dragging belly indicatees that your cat is too fat.得到的匹配结果是belly

匹配基础 规则二

规则二:标准量词是匹配优先的。

完成复杂的正则表达式,我们需要使用星号、加号、问号等元字符。

从名字可以看出:标准匹配量词的结果“可能”不是所有可能匹配中最长的,但它们总是尝试匹配尽可能多的字符,直到匹配上限为止。

举个例子:用\b\w+s\b匹配包含s的字符串,比如regexes,\w+完全能匹配一整个单词regexes,但是如果\w+匹配了一整个单词,s就无法匹配了。为了完成匹配,\w+只能匹配regexes,把s\b留出来。

用[0-9]+来匹配march 1998中的所有数字,1匹配后,实际上已经满足了成功匹配的下限,但由于正则引擎匹配优先,所有会继续匹配998,直到匹配不到连续数字为止。

过度的匹配优先

匹配优先的特性使得量词匹配尽可能多的结果,但实际上它的工作原理是这样的:量词先匹配尽量多的东西,然后根据正则表达式其余的部分按情况“被迫”“交还”出匹配结果。上面的regexes例子就是这样的。但是交还不能破坏匹配成立的必须条件,比如加号的第一次匹配。

再来看看用^.*([0-9][0-9])匹配about 24 char long的过程:

.匹配了一整个字符串以后,第一个[0-9]要求必须匹配一个数字,所以.被迫交出最后一个字符g,但是这不能让[0-9]匹配,所以.继续交还,重复几次以后,交换的字符是4,这下能够匹配了。但是由于第二个[0-9]也要求匹配一个数字,所以,.又被迫交还一个字符2,两个[0-9]都匹配成功了,所以.*实际上匹配了about。

如果用^.[0-9]+来匹配copyright 2003,那么只有3会被[0-9]+匹配到,因为当有两个量词时,采取先到先得的规则,.被迫交还一个字符就能满足后面的+。所以0不会再被交还。

NFA引擎 表达式主导

我们用to (nite|knight|night)来匹配’tonight’。

正则表达式从t开始,每次检查一个字符,如果能够匹配,则开始匹配下一个字符。当遇到多选结构时,NFA的引擎会把nite、knight、night分别当作三个不同的独立的整体去进行匹配。

如果nite匹配不成功,则尝试knight,如果还不成功,最后再尝试night。

表达式的聚焦点从nite到knight再到night,是有表达式的元素控制的,所以这样的匹配方法叫做表达式主导。

DFA引擎 文本主导

与NFA不同,DFA在扫描字符串时,会记录“当前有效”的所有匹配可能而考察的下一个文本字符只会在“当前有效”的匹配可能中继续匹配,至于那些已经无效的匹配可能,就不去管它们。

Tonight

To (nite|knight|night)

这种方式称为文本主导,因为文本的每一个字符都在控制着引擎的行为。

比较NFA与DFA

一般情况下,文本主导的DFA引擎要快些,因为表达式主导的NFA引擎需要对同样的文本进行不同的子表达式的尝试,例如前面的例子,有三个多选分支,night这个文本就被重复进行匹配了三次,比较浪费时间。

另外,NFA引擎必须把整个正则表达式都经历完(一直到正则表达式的最后末尾),才知道匹配的结果。而DFA引擎的原理允许在匹配到没有“有效”的状态时,就可以知道全局匹配的结果了。

但NFA是表达式主导的,所有用户可以通过修改正则表达式来提高效率,所以讨论NFA引擎是很有趣的一件事。

回溯

NFA最重要的性质是,它会依次处理各个子表达式或组成的元素,遇到需要在两个可能成功的可能匹配中,进行选择时,它会选择其一,同时记住另外一个,以备稍后可能的需要。

需要作出选择的情形下包括量词(决定是否尝试另一次匹配)和多选结构(决定选择哪个多选分支,留下哪个稍后尝试)。

不论选择哪一个途径,如果它能匹配成功,而且正则表达式的余下部分也成功了,匹配即告完成,如果正则表达式中余下的部分最终匹配失败,引擎会知道需要回溯到之前作出选择的地方,选择其他备用的分支继续尝试。这样,引擎最终会尝试表达式的所有可能的途径,准确一点来说,是会尝试到匹配完成之前需要的所有途径。

Tonight

To (nitee|knight|night)

回溯的两个要点

面对众多的选择时,哪个分支应当向首先选择?

多选分支就是从左到右的顺序。

如果需要在“进行尝试”和“逃过尝试”之间选择,对于匹配优先量词,引擎会优先选择“进行尝试”,而对于忽略优先量词,会选择“跳过尝试”。

回溯进行时,应该选取哪个保存的状态?

距离当前最近储存的选项就是当本地失败强制回溯时返回的。其实就是后进先出,这个和计算机的堆结构相像。

如果你在每个岔路都撒一堆面包屑,那么如果前面是死路,你只需要沿原路返回,直到找到一堆面包屑为止。

备用状态

用NFA的术语来说,这些面包屑就是备用状态,他们用来标记:在需要的时候,匹配可以从这里重新开始尝试。他们保存了两个位置:正则表达式中的位置,和未尝试的分支在字符串中的位置。

未进行回溯的匹配:

用ab?c匹配abc

进行了回溯的匹配:

用ab?c匹配ac

不成功的匹配:

用ab?c匹配abX

忽略优先匹配:

用ab??C匹配abc

回溯和匹配优先

在回溯的例子中,我们已经看到了?匹配优先和??忽略优先是怎么工作的了,现在来看看星号和加号。

如果认为x*和x?X?X?X?X?……基本等同,那么分析的过程和刚才就几乎是一模一样的,只不过重复了很多遍而已。

用[0-9]+去匹配a 1234 num,匹配完4以后,此时+号可以回溯的备选状态有四个:

1 234

12 34

123 4

1234

因为[0-9]+相当于[0-9][0-9]?[0-9]?………………

使用忽略优先量词

NFA支持忽略优先量词

?就是与对应的忽略优先量词。

+?就是与+对应的忽略优先量词。

??就是与?对应的忽略优先量词。

.*?匹配Billionsand millions of

匹配优先、忽略优先和回溯的要旨

无论是匹配优先,还是忽略优先,都是为全局服务的,如果全局需要,这两种优先方式遇到“本地匹配失败”时,引擎都会回归到备选状态(找回面包屑),然后尝试尚未尝试的路径。所以无论是匹配优先还是忽略优先,只要引擎报告匹配失败,他必然已经尝试了所有可能。测试路径的顺序对于两种优先方式是不同的,但只有在测试了所有可能的路径以后,才会最终报告匹配失败。

如果匹配的结果是唯一的(路径只有一条),那么使用匹配优先还是忽略优先都能找到这个唯一的结果,只不过是测试的次序不同而已。

如果存在不止一个匹配结果:

The name “McDonald’s” is said “makudonarudo” in Japense.

.*匹配最长的结果,.*?匹配最短的结果

多选结构

Perl、PHP、Java、.NET以及其他语言使用的NFA引擎,遇到多选结构时,都是按照从左到右的顺序检查表达式的多选分支。如用subject|date,会先使用subject,如果能够匹配,就继续进行下去,不会再管date,如果不匹配,则回溯并且使用date,所以正则引擎会回溯到存在尚未尝试尝试的多选分支的地方。

当需要匹配Jan 31这样的日期时,我们需要的不是简单的Jan [0123][0-9],因为这样有可能会匹配到Jan 00或Jan 39,而且无法匹配Jan 7这样的日期。

一种方法是把日期拆开,用0?[1-9]匹配可能用0开头的前九天的日期,用[12][0-9]处理十号到二十九号,用3[01]处理最后两天。可是我们应该注意多选结构的顺序,如果用Jan (0?[1-9]|[12][0-9]|3[01])来匹配Jan 31,只会得到Jan 3,因为在第一个多选分支可以成功匹配3.所以我们把能够匹配的最短的数字放在最后面,问题就解决了:

Jan ([12][0-9]|3[01]|0?[1-9])

匹配IP地址

匹配一个ip地址,用点号隔开四个数,例如001.002.003.004

如果用[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*来匹配显然这个表达式不够精致,它甚至可以只匹配三个点…。

第二个想法是把*换成+,这样就能确保有数字了,[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+,当然这样的正则表达式依然不符合要求,因为他会匹配到1234.5678.91234.3215600564这样的组合。

我们应该限定点号之间只能有1个或2个或3个数字,对于支持区间量词的流派,可以用\d{1,3}\.\d{1,3}\.\d{1,3}来匹配,对于不支持区间量词的,可以用\d\d?\d?或者\d(\d?\d)?来替代。

上面的表达式确实可以匹配ip地址了,但现在我们还要进一步,匹配有效的IP地址,该怎

么办呢?

我们关注字段中什么位置可以出现那些数字。IP地址的三位数是不超过255的,所以如果

一个字段只包含一个或者两个数字,就无需担心这个字段的值是否合法,因为一定是合法

的,这个用\d|\d\d就能应付。同样,我们也不必担心以0或者1开头的三位数,因为000-

199都是合法的,所以现在我们的表达式变成了\d|\d\d|[01]\d\d

如果以2开头的三位数字,小于255就是合法的,所以第二位数小于5就代表整个字段合法

,如果第二位是5,则第三位小于6就整个字段合法。这可以表示为2[0-4]\d|25[0-5]

现在我们的表达式就是\d|\d\d|[01]\d\d| 2[0-4]\d|25[0-5] ,然后还可以把前面三个分支

简化成[01]?\d\d?| 2[0-4]\d|25[0-5]

现在是可以匹配一个精准的IP地址了。

处理文件名

去掉文件名开头的路径:

/user/local/bin/gcc变成gcc,我们就可以用.*匹配优先的特性,使得.*匹配一整个路径

,然后再加上/,让正则表达式回溯到最后一个斜杠,也就是逼迫.*交还字符直到遇到最后

一个斜杠。 具体的做法 =~s{^.*/}{}

从路径中获取文件名:

[^/]*$来从路径中获取最后的文件名。

可是大家如果完全明代前面我们说的内容,就会发现这个表达式包括了太多回溯。即使是

短短的/user/local/bin/gcc,在获得匹配结果前,也经历了40多次回溯。

分隔路径和文件名:

^(.*)/(.*)$来分隔最后一个斜杠之前的和之后的内容。

如果更加精确一点,可以使用^(.*)/([^/]*)$

匹配对称的括号

为了匹配val=foo(bar(this),3.7)+2*(that-1);中的(bar(this),3.7)

  1. \(.*\)
  2. \([^)]*\)
  3. \([^()]*\)

    第一个匹配的太长,第二个匹配的太短,第三个只能匹配(this)

    正则表达式无法匹配任意深度的嵌套结构

    但是可以匹配特定深度的嵌套括号

匹配HTML tag

最常见的方法是用<[^>]+>来匹配HTML标签。这通常都能成功。

但是如果标签中又含有>,例如<input name=“dir”value=“>” >这样子就匹配不成

功。

熟悉HTML的同学会发现,=号后面必须会出现单引号或者双引号,所以我们只需要把<>

中的内容分成引用根本和非引用文本,就可以完成匹配所有情况的标签。

<(“[^”]”|’[^’]’|[^”’>])*>

匹配HTML link

在HTML中,<a href=“http://www.oreilly.com”>O’Reilly</a>

我们用<a\b([^>]+)>(.*?)</a>来分别提取链接和链接文本

然后$1=~m{href\s*=\s*(?:”([^”]*)”|’([^’]*)’|([^’”>\s]+))}xi

然后用$+这个变量,这个变量储存的是$1、$2等数字变量中编号最靠后的变量。

这里就是我们想要的URL。

校验HTTP URL

现在我们得到了URL地址,来看看他是否是HTTP URL,如果是,就把它分解为主机名和

路径两部分。

主机名是^http://之后和第一个反斜杠(如果有的话)之间的内容,而路径就是除此之外

的内容,而路径就是除此之外的内容:^http://([^/]+)(/.*)?$

URL中有可能包含端口号,它位于主机名和路径之间,以一个冒号开头

^http://([^/:]+)(:(\d+))?(/.*)?$

perl正则表达式第三周笔记的更多相关文章

  1. 《Linux内核分析》第三周笔记 构造一个简单的Linux系统MenuOS

    构造一个简单的Linux系统MenuOS 一.linux内核源代码简介 三大法宝(存储程序计算机.函数调用堆栈.中断)和两把宝剑(中断上下文的切换:保存现场和恢复现场.进程上下文的切换) 1.在lin ...

  2. 《Linux内核分析》第三周学习笔记

    <Linux内核分析>第三周学习笔记 构造一个简单的Linux系统MenuOS 郭垚 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.stud ...

  3. Linux内核分析第三周学习笔记

    linux内核分析第三周学习笔记 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.co ...

  4. LINUX内核设计与实现第三周读书笔记

    LINUX内核设计与实现第三周读书笔记 第一章 LINUX内核简介 1.1 Unix的历史 1969年的夏天,贝尔实验室的程序员们在一台PDR-7型机上实现了Unix这个全新的操作系统. 1973年, ...

  5. 20145316许心远《Java学习笔记》第三周总结

    20145316许心远<Java程序设计>第3周学习总结 教材学习内容总结 一.定义类: 类定义时使用class关键字 如果要将x绑定到新建的对象上,可以使用"="制定 ...

  6. 读书笔记第三周 人月神话 刘鼎乾 PB16070837

    读书笔记第三周:人月神话   这本书主要讲述了如何管理一个软件开发团队的问题,其中如何提高团队的效率可以说是本书的重点之一了.感觉这本书地中文版翻译得比较晦涩,很多表达比较模糊,看起来有些吃力,因此下 ...

  7. 《Machine Learning》系列学习笔记之第三周

    第三周 第一部分 Classification and Representation Classification 为了尝试分类,一种方法是使用线性回归,并将大于0.5的所有预测映射为1,所有小于0. ...

  8. Linux内核分析——第三周学习笔记20135308

    第三周 构造一个简单的Linux系统MenuOS 计算机三个法宝: 1.存储程序计算机 2.函数调用堆栈 3.中断 操作系统两把宝剑: 1.中断上下文的切换:保存现场和恢复现场 2.进程上下文的切换 ...

  9. 程序设计入门-C语言基础知识-翁恺-第三周:循环-详细笔记(三)

    目录 第三周:循环 3.1 循环 3.2 循环计算 3.3 课后习题 3.4 讨论题(不需要掌握) 第三周:循环 3.1 循环 while循环 语法: while(条件表达式){ //循环体语句 } ...

随机推荐

  1. POJ2553-The Bottom of a Graph

    id=2553">主题链接 题意:求解Bottom(G).即集合内的点能够互相到达. 思路:有向图的强连通.缩点,找出出度为0的点,注意符合的点要按升序输出. 代码: #include ...

  2. CRM odata方法如何使用$top

    odata方法 $top $top1 取1个 ¥top100取100个,放在$select前,中间用&符号隔开. 例如: var activeserviceReq = "/xrmse ...

  3. jquery 获取当前元素的索引值

    $("#lisa > li").mouseover(function(){ alert($("#lisa > li").length); alert ...

  4. Python核心编程读笔 10:函数和函数式编程

    第11章 函数和函数式编程 一 调用函数  1 关键字参数 def foo(x): foo_suite # presumably does some processing with 'x' 标准调用 ...

  5. php mysql_insert_id() 获取为空

    mysql_insert_id() 获取插入数据后的最新的id 遇到问题和解决的步骤如下: 1. 使用后总是返回空的字符串,网上查了一番有人说是id要AUTO_INCREMENT,并且mysql_in ...

  6. 在MyEclipse环境下写Struts,删除项目不干净的问题的解决

    这个头疼的问题弄了好几个小时,终于弄好了.方法如下:1.建立一个新的项目,确认自己已经部署好Struts2的环境(网上有好多教程).运行Tomcat还是会有之前的项目的错误,接下来进行第二步2.将To ...

  7. Java格式化输出

    Java的格式化输出等同于String.Format,与C有很大的相似,比如 System.out.printf("%8.2f", x);在printf中,可以使用多个参数,例如: ...

  8. hadoop笔记之Hive的数据存储(桶表)

    Hive的数据存储(桶表) Hive的数据存储(桶表) 桶表 桶表是对数据进行哈希取值,然后放到不同文件中存储. 比如说,创建三个桶,而创建桶的原则可以按照左边表中学生的名字来创建对应的桶.这样子把左 ...

  9. J2SE知识点摘记(十五)

    1.        字节流和字符流的转换 以字符为导向的stream基本上有与之相对应的以字节为导向的Stream,两个对应类实现的功能相同,只是操作时的导向不同 字节输入流转换为字符输入流: Inp ...

  10. 要不要用STL的问题——真理是越辩越明的~

    QtWidgets的维护者 Marc Mutz 有一篇博客比较详尽的介绍了 Qt自己的容器.介绍了何时用什么比较好https://marcmutz.wordpress.com/effective-qt ...