刨根究底正则表达式之二——正则表达式基础

正则表达式基础

一、正则表达式构成

1.

正则表达式中的语法元素,从是否具有特殊含义的角度进行分类,可分为下列两大类、共五种语法元素:

1)不具有特殊含义的语法元素

(1)  字面字符(文本字符)不具有特殊含义的单个字符,代表字符自身(即字符字面值);

(2)  普通转义序列:由转义前导符\后跟元字符所组成的字符序列,将具有特殊含义的元字符,转义为(即转换为)不具有特殊含义的字符本身(即字符字面值);

2)具有特殊含义的语法元素

(1)  元字符:具有特殊含义的单个字符,包括:\、(、)、[、]、{、}、.、-、*、+、?、|、^、$;

(2)  元转义序列:由转义前导符\后跟单个字符或多个字符组成,具有特殊含义,包括:\0octal-num、\num、\a、\A、\b、\b{}、\B、\B{}、\cX、\C、\d、\D、\e、\E、\f、\F、\g{}、\gnum、\G、\h、\H、\k{}、\k<>、\k‘‘、\K、\l、\L、\n、\N、\N{}、\o{octal-num}、\pP、\p{}、\PP、\P{}、\Q、\r、\R、\s、\S、\t、\u、\U、\v、\V、\w、\W、\xhex-num、\x{hex-num}、\X、\z、\Z等;

(3)  特殊构造(特殊结构)由多个元字符和/或普通字符组成,具有特殊含义,包括:字符组[xyz]或[^xyz]、捕获分组(sub-regex)、命名捕获分组(?<name>sub-regex)、非捕获分组(?:sub-regex)、预查分组(即环视分组)(?=sub-regex)或(?!sub-regex)或(?<=sub-regex)或(?<!sub-regex)、固化分组(即原子分组)(?>sub-regex)、嵌入条件分组(?(condition)true_sub-regex|false_sub-regex)、内联修饰选项与取消内联修饰选项分组(?modifier-modifier)、注释分组(?#comment)、分支复位分组(?|sub-regex)、表达式引用分组(?R)或(?num)、平衡分组(?<-name>sub-regex)等。

笨笨阿林原创文章,转载请注明出处)

2.

从匹配的是位置还是字符的角度来分类,可分为如下四大类:

1)匹配字符的语法元素

(1)  字面字符(文本字符)代表字符自身(即字符字面值);

(2)  普通转义序列:将具有特殊含义的元字符,转义为(即转换为)不具有特殊含义的字符本身(即字符字面值);

(3)  元字符:.;

(4)  下面这些元转义序列:

固定字符:\a、\b(字符组内部)、\e、\f、\n、\r、\t、\v(非Perl系);

字符组简记:\d、\D、\h、\H、\N{}、\p{}与\pP、\P{}与\PP、\s、\S、\v(仅Perl系)、\V、\w、\W

进制转义字符:\octal-num(Perl系中也可写作\o{octal-num})、\xhex-num(Perl系中也可写作\x{hex-num})、\uhex-num(非Perl系,Ruby1.9+等个别语言中还可写作\u{hex-num});

控制字符:\cX系列;

其他:\C、\N、\R、\X。

2)匹配位置的语法元素

(1)  下面这些元字符:

^、$

(2)  下面这些元转义序列:

锚点:\A、\z、\Z、\b(字符组外部)、\b{}、\B、\B{}、\G;

其他:\<、\>。

(3)  预查分组:

(?=sub-regex)、(?!sub-regex)、(?<=sub-regex)、(?<!sub-regex)。

3)既可能匹配字符,也可能匹配位置的语法元素

(1)  由下限次数为0的量词所限定的子表达式,下限次数为0的量词包括:?、*、{0,}、{0,m}、{,m}(逗号“,”前面为空的这种写法仅部分正则引擎支持,不推荐这种写法);

(2)  下面这些元转义序列:

引用:\num、\g{num}、\gnum、\k{name}、\k<name>、\k‘name‘(如果引用的是文本,则匹配字符,如果引用的是位置或空字符串,则匹配的是位置);

(3)  特殊构造(特殊结构)捕获分组(sub-regex)、命名捕获分组(?<name>sub-regex)、非捕获分组(?:sub-regex)、固化分组(即原子分组)(?>sub-regex)、嵌入条件分组(?(condition)true_sub-regex|false_sub-regex)等,当这些分组中的sub-regex为空时,匹配的是位置;不为空时,若sub-regex匹配字符,则这些分组匹配的是字符,否则匹配的是位置。

4)既不匹配字符,也不匹配位置的语法元素

除上述语法元素之外的其他语法元素,这包括:\K、内联修饰选项与取消内联修饰选项分组(?modifier-modifier)、注释分组(?#comment)等。

3.

注意,语法元素有时也会称之为子表达式;当然,子表达式的概念要比语法元素更为丰富,涵盖面更广。因此,需根据上下文予以准确理解。

二、字符串构成

1.

从正则表达式的角度来看,字符串通常由位置和字符所共同构成,但空字符串仅由单个位置构成(该位置既是空字符串的起始位置,也是空字符串的结束位置,可同时匹配表示字符串起始位置的元字符^和表示字符串结束位置的元字符$)。

对于字符串“Regex”而言,是由五个字符以及六个位置构成的,理解这一点对于正则表达式的匹配原理的理解很重要。

2.

字符串中的位置,其实也是组成该字符串的字符的索引,因此,位置0就是用来索引(即定位)字符R的索引0。字符串“Regex”始于索引0(即位置0)处,止于索引5(即位置5)处。

当正则引擎在字符串中查找匹配时,可以认为在字符串中有一个匹配定位指针,该指针可以在字符串中的各个位置之间移动(一般是从左到右依次移动,但回溯时也会从右向左移动;另外,.Net中还支持从右向左匹配)。

3.

查找匹配过程中,下一次匹配的起始位置与前一次匹配的结束位置往往是相同的:

正则式:/regex/

字符串:regexregex

找到第一个子字符串"regex",开始于位置0结束于位置5

找到第二个子字符串"regex",开始于位置5结束于位置10

笨笨阿林原创文章,转载请注明出处)

三、匹配过程与匹配定位指针、匹配控制权

1.

匹配过程从字符串的角度来看的话,必然总是从字符串中的一个位置开始匹配的,可能是从字符串的起始位置匹配,也可能是从字符串中间的某两个字符之间的位置开始匹配,甚至可能是从字符串的结束位置开始匹配(.Net中支持从右向左匹配)。当然,绝大部分情况下,均是从字符串的起始位置开始匹配的。

当在某个位置尝试匹配失败,正则引擎将移动字符串中的匹配定位指针到字符串中的下一个位置开始继续尝试匹配。这样逐个位置尝试,直到获得匹配,或者一直到字符串结尾仍未获得匹配则报告匹配失败。

2.

匹配过程从正则表达式的角度来看的话,必然总是从正则表达式的起始位置从左至右逐个语法元素开始尝试匹配的(但多选分支结构中的情况稍微复杂些:传统型NFA正则引擎由于遵循“最左先到先得原则”,一旦其中某个分支获得了匹配,将不会继续尝试匹配剩下的分支;而DFA和POSIX NFA正则引擎由于遵循“最左最长原则”,必须选择各个分支中所获得的最长匹配,因此会逐个分支尝试匹配)。

正则表达式中的某个语法元素一旦在字符串中获得了匹配(若该语法元素后面有量词限定的话,需满足其重复次数,且有可能存在回溯,详见后文解释),则表示该语法元素成功获得了匹配,于是匹配控制权转移到下一个语法元素,重复该过程。

原则上,匹配控制权一旦从某个语法元素转移出去,则该语法元素不能再次重新获得。不过,懒惰量词形成的回溯例外(懒惰量词所限定的语法元素一旦获得了该量词的下限次匹配之后,会先将匹配控制权转移给紧随其后的语法元素,若紧随其后的语法元素无法匹配,则会将匹配控制权返回给该语法元素)。(如果暂时看不明白没关系,后文都会有详细解释)。

若正则表达式中的某个必须匹配的语法元素(而由下限次数为0的量词所限定的语法元素则为可选匹配)一旦在字符串中无法获得匹配,则该正则表达式匹配失败。

四、占有字符(消费字符与消耗字符)匹配和不占有字符(零宽度)匹配

1.

正则表达式匹配过程中,若其中的某个语法元素匹配到的是字符,而非位置,并且在字符串中移动了匹配定位指针,此时可分为两种情况:

1)  所匹配的字符被保存到了最终的匹配结果中(即返回了所匹配到的字符),那么就认为该子表达式消费了这些字符;

2)  所匹配的字符未被保存到最终的匹配结果中(即没返回所匹配到的字符),那么就认为该子表达式消耗了这些字符(比如位于元转义序列\K之前的子表达式)。

无论是消费了字符,还是消耗了字符,均属于占有了字符。

如果该子表达式匹配的仅仅是位置,或者虽然匹配了字符,但最终并不实际移动字符串中的匹配定位指针(比如预查分组),那么就认为这个语法元素是不占有字符的,即属于零宽度的。

占有字符还是不占有字符,最终以是否实际移动了字符串中的匹配定位指针为准。

2.

占有字符是互斥的,不占有字符是非互斥的。

所谓互斥指的是一个字符,只能由一个语法元素匹配,一旦被某个语法元素匹配后占有了,则不能为其他语法元素所匹配占有;所谓非互斥指的是一个位置,可以同时由多个不占有字符(即零宽度)的语法元素匹配。

笨笨阿林原创文章,转载请注明出处)

五、八大原则简介

1.

受《精通正则表达式》一书中“最左原则”、“最长原则”以及衍生的“最左最长原则”的启发,在此基础上我进一步推广扩展,总结为八大原则。其中包括六大基本原则与两大衍生原则,先简要介绍如下(后文结合语法元素会有详细解释):

六大基本原则:

1)  最左原则:在一个字符串中,若一个正则表达式可能有多个匹配结果时,其中最靠近字符串左边的起始位置的那个匹配结果总是会优先于其他的匹配结果被返回;

2)  最长原则(即长度优先原则)如果在字符串中的某个位置存在多个可能的匹配,将返回最长文本(即最多字符)的那个匹配;

3)  先到先得原则(即顺序优先原则)在同一个位置上,如果有多个长度不同的匹配结果,将返回最先获得匹配的结果,或前后两个由贪婪量词或懒惰量词所限定的子表达式发生匹配冲突时,后者仅获得其下限次数的匹配,而前者将获得超过其上限次数的尽可能多的匹配;

4)  逐位置依次尝试匹配原则:匹配总是从字符串的起始位置(即位置0)开始,从左到右地逐个位置尝试匹配整个正则表达式;

5)  整体匹配优先原则:整个正则表达式获得匹配的优先级要高于贪婪量词所限定的子表达式;

6)  占有匹配优先原则:整个正则表达式获得匹配的优先级要低于占有量词所限定的子表达式。

两大衍生原则:

1)  最左最长原则:非全局模式下,如果在字符串中的多个位置中的每个位置均有多个可能的匹配文本,DFA和POSIX NFA引擎会优先选择最靠左边位置的所有可能的匹配文本当中最长的文本;

2)  最左先到先得原则:非全局模式下,如果在字符串中的多个位置中的每个位置均有多个可能的匹配文本,传统型NFA引擎会优先选择最靠左边位置的所有可能的匹配文本当中最先获得匹配的文本。

2.

这些原则看似平淡无奇,但正如“两点间直线距离最短”这样显而易见的几何学公理,却是支撑起整个宏伟的欧几里得几何学的基石一样,这八大原则也是正则引擎匹配机制的基础,理解它们是理解正则引擎匹配行为的关键。

笨笨阿林原创文章,转载请注明出处)

(未完待续)

预告:《刨根究底正则表达式》系列的下一篇将正式开始逐个介绍各正则表达式语法元素;而《刨根究底字符编码》系列的下一篇将重点介绍UTF-16编码,敬请关注!】

时间: 2024-11-10 00:40:35

刨根究底正则表达式之二——正则表达式基础的相关文章

正则表达式(二)——元字符

正则中的字符分为元字符和一般字符,一般字符就匹配它本身(写什么就是什么,比如正则中写字母‘a’,匹配的时候就匹配字母‘a’),而元字符是正则表达式中最基本的内容,也是最核心的内容,基本上所有的正则表达式都由“元字符”组成,元字符就是正则表达式的基础.在此文中,会涉及到字符组,多选结构,点号以及预定义字符集的基本介绍. 什么是“元字符” 所谓元字符就是指那些在正则表达式中具有特殊意义的专用字符.这些元字符(专用的字符或则字符组),可以代替多个一个或多个字符.下面用一个图来展示一下基本的元字符 上面

shell脚本编程之正则表达式(二)(扩展正则表达式、sed)

shell脚本编程之正则表达式(二) 一.前言 ? 本文主要是对扩展正则表达式的介绍,同时,继续按照上篇文章的风格介绍sed文本处理工具,sed作为shell编程中"三剑客"之一,在对文本处理上有巨大作用.关于正则概念以及grep命令结合正则使用的案例请参照:https://blog.51cto.com/14557673/2455588 二.扩展正则表达式 ? 扩展正则表达式主要是为了简化指令而产出的.例如,使用基础正则表达式查询文件中空白行与行首为#号之外的行(一般用于查看生效的配置

正则表达式(二)

注:元字符中的字母不缺分大小写. 元字符 说明 ^ 匹配字符串的开始. $ 匹配字符串的结束. . 匹配除换行符以外的任意字符. \d 匹配数字. \b 匹配单词的开始或结束. \s 匹配任意的空白字符. \w 匹配字母.数字.下划线或汉字. 注: "^"  如果设置RegexOptions.Multiline属性时,也可以匹配"\r"或"\n"之后的字符串. 例如: string tel1 = "bc \rj5k\njg";

深入浅出之正则表达式(二)转载http://www.cnblogs.com/dragon/archive/2006/05/09/394923.html

深入浅出之正则表达式(二) 前言:       本文是前一片文章<深入浅出之正则表达式(一)>的续篇,在本文中讲述了正则表达式中的组与向后引用,先前向后查看,条件测试,单词边界,选择符等表达式及例子,并分析了正则引擎在执行匹配时的内部机理.        本文是Jan Goyvaerts为RegexBuddy写的教程的译文,版权归原作者所有,欢迎转载.但是为了尊重原作者和译者的劳动,请注明出处!谢谢!   9.      单词边界 元字符<<\b>>也是一种对位置进行匹

python 基础 8.0 regex 正则表达式--常用的正则表达式

一. python 中常用的正则表达式 二. 正则表达式的网站,可以进行在线正则匹配 https://regex101.com/ 1. 使用方法及正则介绍 1> '.'  匹配任意除换行符外(\n)的任意字符 '*'  匹配前一个字符0次或任意多次 2>\b  匹配数字0-9 3>\D  匹配非数字  [^\b] 4>\s  匹配空格字符 5> \S 匹配非空白字符 [^\s]* 6> \w  匹配单词字符 [a-z] [A-Z][0-9] 7> \W  匹配非单

Linux篇 | 文本处理工具和正则表达式(二)

文本处理工具和正则表达式 Linux文本处理三剑客 grep:文本过滤(模式:pattern)工具 >grep, egrep, fgrep(不支持正则表达式搜索) sed:stream editor,文本编辑工具 awk:Linux上的实现gawk,文本报告生成器 本篇主要讲述grep 一.grep 作用:文本搜索工具,根据用户指定的"模式"对目标文本逐行进行匹配检查:打印匹配到的行.(模式就是"正则表达式") 模式:由正则表达式字符及文本字符所编写的过滤条件

String类 (二) ~正则表达式

正则表达式 1. 概述 正则表达式本质就是一个 字符串,用于进行数据格式的验证. 通常情况下,正则表达式使用  ^ (异或符号)开头,使用$  (美元符号) 结尾.        可以省略,但是推荐写上 2.常用规则 字符类: [abc]     -表示可以出现a.b 或c中的任意字符 [^abc]   -表示可以出现任意字符,除了a.b 及c [a-zA-Z]  -表示可以出现a到z 和 A到Z之间的任意字符.即可以出现所有的字母. 预定字: \d  -表示可以出现任何数字,相当于[0~9]

shell编程之正则表达式(一)基础正则表达式

正则表达式之前学习了 Shell 脚本的基础用法,已经可以利用条件判断.循环等语句编辑 Shell 脚本.接下来我们将开始介绍一个很重要的概念--正则表达式(RegularExpression,RE).正则表达式的定义正则表达式又称正规表达式.常规表达式.在代码中常简写为 regex.regexp 或 RE.正则表达式是使用单个字符串来描述.匹配一系列符合某个句法规则的字符串,简单来说, 是一种匹配字符串的方法,通过一些特殊符号,实现快速查找.删除.替换某个特定字符串.正则表达式是由普通字符与元

Python学习-38.Python中的正则表达式(二)

在Python中,正则表达式还有较其他编程语言有特色的地方.那就是支持松散正则表达式了. 在某些情况,正则表达式会写得十分的长,这时候,维护就成问题了.而松散正则表达式就是解决这一问题的办法. 用上一次分组的代码作为例子: 1 import re 2 userinput = input("please input test string:") 3 m = re.match(r'(\d{3,4})-(\d{8})',userinput) 4 if m: 5 print('区号:' + m