我们使用正则表达式,熟练掌握各种功能和结构只是手段,解决实际的问题才是真正的目的。要解决真正的问题,就必须有解决问题的思路,正则表达式的功能,说到底,可以归纳为三种逻辑,为了表述方便,我们分别称为与、或、非。
逻辑关系
|
说明
|
与
|
在某个位置,某些元素(字符、字符组或者子表达式)必须出现
|
或
|
在某个位置,某个元素或许不出现,或许不出现,或许长度不固定;要出现的,是某几个元素中的一个
|
非
|
在某个位置,某些元素不能出现
|
一般来说,正则表达式千变万化,总是这三种逻辑的组合。比如匹配双引号字符串
"quoted string"
逻辑关系
|
分析
|
与
|
首尾的双引号字符必须出现
|
或
|
两个双引号之间的字符个数是不确定的(如果是空字符串””,则两个双引号之间没有字符)
|
非
|
两个双引号之间不能出现双引号字符
|
再比如匹配html中的open-tag(比如<h1>)和close-tag(比如</h1>):
逻辑关系
|
分析
|
与
|
首尾必须分别是<和>,如果是close-tag,则<之后必须出现/
|
或
|
<和>之间必须出现至少一个字符(<>不是一个合法的tag)
|
非
|
<之后不能是/字符,如果是open-tag,<之后不能出现/
|
下面我们来讲解三种逻辑的对策。
与
“与”是正则表达式中最普通的逻辑关系。一般来说,如果正则表达式中的元素没有任何量词(quantifier,比如*、?、+)修饰,就是“与”关系。比如『<』,就表示“这里必须出现<字符”;『cat』,就表示“这里必须依次出现c、a、t,3个字符”。
不过“与”的情况并没有这么简单,有时候,“必须出现”的是若干个元素,或者说,几个元素必须同时出现,但它们之间并不相连,这是非常容易犯错的时候,不过现在我们不举具体的例子,稍晚一点再说。
或
“或”是正则表达式中最灵活的逻辑关系。正则表达式能应对各种不同的文本,“或”功能不可或缺。
如果“或”的意思是,元素可以出现,也可以不出现,或者出现的次数不确定,可以用量词来表示“或”关系。比如表达式『a?』,表示在此处,字符a可以出现,也可以不出现;表达式『(ab)+』,表示在此处,字符串ab必然要出现1次,也可以出现无限多次。
如果“或”的意思是,可以出现的是某几个元素中的一个,则应该使用字符组或者多选结构。当元素都是单个字符时,就应该使用字符组『[…]』:比如匹配单词cat或者cut,除去开头的a、结尾的t是固定的,之中“或许出现a,或许出现u”,所以应当使用字符组『[au]』,整个正则表达式就是『c[au]t』。当元素不只单个字符(只要有一个元素不只单个字符)时,就应该使用多选结构『(…|…)』:比如不但要匹配单词cat或者cut,还要匹配单词chart、conduct和court,出去开头的a、结尾的t是固定的,之中“或许出现a,或许出现u,或许出现har,或许出现onduc,或许出现our”,这时候就应该使用多选结构『(a|u|har|onduc|our)』,整个正则表达式就是『c(a|u|har|onduc|our)t』。
当然,多选分支也可以表示字符组,比如『[au]』就可以表示为『(a|u)』,两者的功能是完全等价的,但是字符组的效率更高,也更直观(毕竟,大家都习惯了简单的『[au]』,而看到『(a|u)』则多半要想一想。
在实践中,“与”和“或”经常同时出现,而且关系不那么简单,下面举一个例子说明:为了隐藏真实的结构,我们需要用URL进行伪装,比如这个URL pattern:/foo/bar_tmp.php。
在真正的系统里,foo是模块名,bar是控制器名,tmp是方法名。合法的URL并不要求3个名字每次都出现,可以只出现控制器名(/foo),也可以只出现控制器名和模块名(/foo/bar.php),也可以3者都出现(/foo/bar_tmp.php)。
这里的模块名、控制器名、方法名,都可以用『[a-z]+』匹配,这里为说明方便,我们暂且用foo、bar、tmp代替对应的表达式。初看起来,这个表达式只包含“与”和“或”两种关系:
逻辑关系
|
分析
|
与
|
/foo必须出现
|
或
|
/bar、_tmp、.php都是可选出现的
|
所以,正则表达式是『/foo(/bar)?(_tmp)?(\.php)?』。
这个表达式确实可以匹配/foo、/foo/bar.php和/foo/bar_tmp.php,但是,它也可以匹配/foo_tmp、/foo/bar_tmp等形式,虽然这些形式并不是合法的。
仔细研究之后,我们发现,“与”和“或”的关系并没有那么简单,而应该是这样的:
逻辑关系
|
分析
|
与
|
/foo必须出现
|
或
|
/bar和.php是可选出现的,但必须同时出现,或同时不出现(与)
|
在/bar和.php都出现的前提下,_tmp才可以出现(或)
|
/foo必须出现,这很好表示,暂且不去管它;/bar和.php如果出现,必须同时出现,所以它们应该作为一个元素,写作『(/bar.php)』;整个元素可选出现,所以给它添加量词,得到『(/bar.php)?』;最后,在/bar和.php都出现的前提下,_tmp才可以出现,所以将『(_tmp)?』填充到『(/bar.php)?』,得到『(/bar(_tmp)?.php)?』,最后加上开头的/foo,整个表达式就是『(/bar(_tmp)?.php)?』。到此,整个关系终于完整表达出来,表达式也不会发生错误匹配。
非
“非”是正则表达式中最难处理的逻辑关系。因为没有直接对应的结构,“非”的处理比较吃力。
最简单的“非”,意思是此处不能出现某个字符,这一点通常很直观,似乎用排除型字符组『[^…]』就可以解决。比如双引号字符串的匹配,首尾两个双引号很容易匹配,其中的内容肯定不是双引号(暂时不考虑转义的情况),所以可以用『[^"]』表示即可,其长度不确定,所以用*来限定,所以整个表达式就是『"[^"]*"』,非常简单。
但是,事情果真都如此简单吗?我们仍然举cat和cut的例子,如果仍然希望匹配c开头、t结尾的单词,但不希望匹配cut,可以写成『c[^u]t』,是否就可以了?
这个表达式的意思是:最开头的字母是c,之后是一个不为u的字符,之后是t。没错,它确实不会匹配cut,也可以匹配cat。但是,chart、conduct、court等等,它也没法匹配,因为[^u]的意思是:匹配一个不是u的字符。
那么,把『[^u]』改成『[^u]+』好了,这样应该就可以解决问题了。但是真的如此吗?『[^u]+』的意思是,一个或若干(最多到无穷)个字符,但每一个字符都不能是u。所以,尽管『c[^u]+t』能匹配cat和chart,却不能匹配conduct和court。
看来,“非”真是比较难对付,让人非常纠结。好在,也不是没有办法解决它。回复到与-或-非的观点,分析要实现的功能:
逻辑关系
|
分析
|
与
|
以c开头,以t结尾
|
或
|
c和t之间可以出现的字母必须多于一个,没有上限
|
非
|
c和t之间不能只有一个字符u
|
如果只考虑“与”和“或”两个逻辑,表达式很好写,是『c[a-z]+t』,再把剩下的条件附加上去,就可以解决问题了。我们仔细看“非”的条件:c和t之间不能只有一个字符u。既然『[^u]+』表达的并不是这个意思,我们不妨换一种表述法:在c之间的位置向后看,不能出现cut。这一点,正好对应否定顺序环视(positive look-ahead)功能,『(?!cut)』就是用来进行这种判断的,它判断之后的字符串能不能由cut匹配,但并不真正真正进行匹配,也不会移动“当前位置”。所以我们将它放在表达式的最开头,得到『(?!cut)c[a-z]+t』。这个表达式的逻辑是:只有在当前位置右侧字符串不能由cut匹配的情况下,才从这里开始,向右尝试用c[a-z]+t。
如果我们更进一步,需要排除掉cat和cut,可以把否定顺序环视改为『(?!c[au]t)』。这样就能保证,匹配到的肯定不是cat或者cut。
更复杂一点,如果我们要验证这样一个字符串:它全部由小写字母构成,长度不超过12位,其中不能包含unfavored或者unwanted。也可以照章处理,先匹配“长度不超过12位”的小写字母『[a-z]{,12}』,然后写出匹配“不需要匹配内容”的正则表达式,『(unfavored|unwanted)』,再用否定顺序环视将它“排除”即可,只是这次要注意,不能直接写『(?!(unfavored|unwanted))』,因为它只能排除『(unfavored|unwanted)』出现在字符串开头的情况,为了排除它出现在字符串中的情况,我们要把否定顺序环视改为『(?![a-z]*(unfavored|unwanted))』,这样就确保完整的“排除”,整个表达式就是『(?![a-z]*(unfavored|unwanted))[a-z]{,12}』。
总结一下,正则表达式中的“非”,除去能用排除型字符组直接表示的,复杂一点的“非”逻辑都是按照这样的思路进行的:先用一个正则表达式准确匹配需要“排除”的字符串,再用环视功能排除掉它——“非”确实是正则表达式中,最难处理的逻辑关系,好在它并不复杂,而且,除去一些比较古老的工具(比如Apache 1.3),现在各种工具和语言,基本都支持这种功能。
原文地址:http://www.infoq.com/cn/news/2011/04/regular-expressions-4
相关推荐
正则表达式教程:30分钟让你精通正则表达式语法 _
综上所述,"正则表达式转换工具"是处理文本数据的强大辅助,它简化了正则表达式的构造过程,使得非专业人士也能方便地利用正则表达式的强大功能。对于编程人员来说,熟练掌握正则表达式不仅可以提高工作效率,还能...
1. 创建正则表达式:定义你要匹配的模式,例如`^[\d]{3}-[\d]{4}$`用于匹配美国电话号码格式。 2. 编译模式:将正则表达式编译成一个可以执行的对象,以提高后续匹配的速度。 3. 执行匹配:使用编译后的对象在目标...
正则表达式语法:正则表达式主要语法详解+编程知识+技术开发; 正则表达式语法:正则表达式主要语法详解+编程知识+技术开发; 正则表达式语法:正则表达式主要语法详解+编程知识+技术开发; 正则表达式语法:正则...
- `Regex`类:这是处理正则表达式的核心类,提供了多种与正则相关的静态方法和实例方法。 - `Match`类:表示正则表达式的一个匹配结果。 - `MatchCollection`类:存储多个匹配结果的集合。 2. **正则表达式语法*...
1. **语法兼容性**:如描述所述,PCRE库的正则表达式语法与Perl语言高度兼容,这意味着开发者可以利用Perl中广泛使用的正则表达式语法,如贪婪和非贪婪量词、分支选择、反向引用等。 2. **Unicode支持**:PCRE库...
标题中的“pb 使用正则表达式源码pbregexp”指的是在PowerBuilder(简称pb)环境中,利用名为“pbregexp”的正则表达式组件来实现源代码级别的正则表达式操作。PowerBuilder是一款流行的可视化的、面向对象的软件...
然而,对于MFC开发者来说,使用MFC内建的正则表达式类库往往更加方便,因为它们与MFC的其他组件更紧密地集成在一起。 在MFC中,正则表达式的实现可能包括以下类: 1. `CRegEx`:这是MFC中的核心正则表达式类,用于...
JAVA正则表达式应用:任意输入一串字符串 如何输入exit退出程序;从输入的字符串中判断是否包含手机号码 正则表达式可以使用" +86| 86 1 d{10}" 如果包含请将其在控制台打印出来 否则输出不包含字符串 ...
总的来说,正则表达式转NFA的实现是理论与实践的结合,它涉及编译原理、形式语言和自动机理论等领域的知识,对于理解和处理字符串模式匹配问题具有深远的意义。在实际应用中,这一转换过程常被用于文本分析、搜索...
为了帮助开发者更好地掌握Java正则表达式技术,我们提供了一系列的Java正则表达式技巧大全,包括《Java正则表达式技巧大全》、《Java数据结构与算法教程》、《Java操作DOM节点技巧总结》、《Java文件与目录操作技巧...
在正则表达式中,还有一些特定的元字符,例如数字字符(\d)、非数字字符(\D)、数字、字母或下划线(\w)、非数字、字母或下划线(\W)、空白字符(\s)、非空白字符(\S)等。这些元字符可以用于匹配特定的字符或...
在Java中使用正则表达式来判断字符串是否符合整数、小数或实数的格式是一种常见且有效的做法。在编程中,我们经常需要对输入的字符串进行格式验证,以确保它们符合预期的数值格式,尤其是在处理财务数据、用户输入...
- 易语言的正则表达式支持可能与其他语言有所不同,需要查阅易语言文档以了解具体实现。 - 考虑性能问题,对于大量文本的处理,优化正则表达式和替换策略是很重要的。 5. **应用场景** - 数据清洗:去除或替换...
8. Unicode汉字范围:`/^[u4e00-u9fa5],{0,}$/` 和匹配中文字符的正则表达式:`[\u4e00-\u9fa5]` - 这两个正则表达式用于检测字符串中是否包含中文字符。 9. 匹配双字节字符:`[^\x00-\xff]` - 用于识别多字节字符...
3. 匹配空白行:`ns*r` - 此正则表达式匹配包含任意数量空格、制表符或其他空白字符的行。`n`匹配换行符,`s`匹配任何空白字符,`*`表示前面的元素可以出现零次或多次,`r`在某些环境(如Python)中代表换行符。 4....
例如,如果我们想验证一个字符串是否为有效的电子邮件地址,可以使用以下正则表达式: ```java String email = "example@example.com"; boolean isValid = email.matches("\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\....
本文将深入探讨VB.NET中的正则表达式以及如何通过源码实现一个正则表达式生成与测试的工具。 一、VB.NET中的正则表达式基础 1. 正则表达式类库:VB.NET中的Regex类库提供了一系列方法和属性,如Match、Matches、...