QCon北京「鸿蒙专场」火热来袭!即刻报名,与创新同行~ 了解详情
写点什么

正则表达式(四):正则表达式的与或非

  • 2011-04-04
  • 本文字数:3373 字

    阅读完需:约 11 分钟

我们使用正则表达式,熟练掌握各种功能和结构只是手段,解决实际的问题才是真正的目的。要解决真正的问题,就必须有解决问题的思路,正则表达式的功能,说到底,可以归纳为三种逻辑,为了表述方便,我们分别称为与、或、非。

逻辑关系

说明

在某个位置,某些元素(字符、字符组或者子表达式)必须出现

在某个位置,某个元素或许不出现,或许不出现,或许长度不固定;要出现的,是某几个元素中的一个

在某个位置,某些元素不能出现

一般来说,正则表达式千变万化,总是这三种逻辑的组合。比如匹配双引号字符串

“quoted string”

逻辑关系

分析

首尾的双引号字符必须出现

两个双引号之间的字符个数是不确定的(如果是空字符串””,则两个双引号之间没有字符)

两个双引号之间不能出现双引号字符

再比如匹配 html 中的 open-tag(比如

)和 close-tag(比如

逻辑关系

分析

首尾必须分别是 < 和 >,如果是 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),现在各种工具和语言,基本都支持这种功能。

2011-04-04 00:0062946

评论 1 条评论

发布
用户头像
(?![a-z]*(unfavored|unwanted))[a-z]{,12}
这个表达式写的有问题,[a-z]{,12} 这种写法 Python 才支持 ,但是如果是 Python 会匹配到 unfavored 或 unwanted 非首个单词后面的部分,感觉断言位置匹配是逐个位置尝试匹配,满足表达式就行,而非先匹配表达式再决定位置。
2019-08-11 23:03
回复
没有更多了
发现更多内容

个推漫话知识图谱:《女心理师》中的智能语音识别系统如何实现?

个推

机器学习 nlp 知识图谱

当 Redis 发生高延迟时,到底发生了什么

程序员历小冰

redis 延迟 28天写作 12月日更

Kubernetes中的亲和性与反亲和性

xcbeyond

kubernete 28天写作 12月日更

世界女性科技群落(四):技术与流行文化顶格发展,日韩女性的科技暗面

脑极体

资产租赁管理系统解决方案

低代码小观

资产管理 CRM 企业管理系统 融资租赁 CRM系统

百度CTO王海峰:AI大生产平台再升级 助力中国科技自立自强

百度大脑

人工智能

如何促进用户首次下单?

石云升

AARRR 产品思维 28天写作 产品增长 12月日更

Go 软件设计之道

宇宙之一粟

Go 语言 12月日更

研究了一圈React学习资料,发现最好用的React教程还是这个

sai

11月云短信报告出炉,腾讯云再次蝉联到达率冠军

博睿数据

忆父亲

wood

28天写作 父亲

都2022年了,这个20篇Linux内存管理的期刊论文,你读了吗?

奔着腾讯去

Linux Kenel 内存映射 内存池 内存页

设计消息队列存储消息数据的 MySQL 表格

奔奔

Java 必看的 Spring 知识汇总!

CRMEB

Fortinet :《2021 年OT与网络安全现状报告》 之「要点综述」

喀拉峻

网络安全

银行兴起数字极简风:“智能手机App恐惧症”终于有救了

CECBC

Spring框架基础知识(01)

海拥(haiyong.site)

28天写作 12月日更

语音信号处理14:语音信号的特征应用

轻口味

28天写作 12月日更

Hadoop学习过程中遇到的错误及解决方法

阿丞

hadoop hdfs mapreduce YARN

一文带你了解数据库设计基础

坚果

数据库 28天写作 12月日更

云原生促进基础设施变革,百度“磐玉”蜂巢服务器正式发布

科技热闻

Python代码阅读(第73篇):字符串字节数

Felix

Python 编程 字符串 阅读代码 Python初学者

基于kali的域控环境搭建——黑盒测试环境搭建

网络安全学海

黑客 网络安全 信息安全 渗透测试 WEB安全

年底了,聊聊述职

CatTalk

职场

信通院首批AIOps系统和工具评估,博睿数据获评异常检测模块“全面级”

博睿数据

2021百度AI开发者大会在元宇宙举办

百度大脑

人工智能

政法重点关注人员管控系统开发,跨部门大数据办案平台建设

a13823115807

Nginx+IIS做站点访问负载均衡

为自己带盐

nginx dotnet 28天写作 12月日更

高性能队列Disruptor在测试中应用

FunTester

Disruptor 测试 性能测试 高性能队列 FunTester

Hoo虎符研究院 | 币圈后浪——Swivel Finance

区块链前沿News

Hoo虎符 虎符交易所 虎符研究院

Kubernetes 与 OpenYurt 无缝转换(命令式)

阿里巴巴云原生

阿里云 容器 云原生 openyurt

正则表达式(四):正则表达式的与或非_Java_余晟_InfoQ精选文章