【ArchSummit】如何通过AIOps推动可量化的业务价值增长和效率提升?>>> 了解详情
写点什么

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

  • 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:0061163

评论 1 条评论

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

mysql存储引擎

急需上岸的小谢

9月月更

概述大数据技术在智能运维中四大挑战

穿过生命散发芬芳

智能运维 9月月更

2022年中国新能源汽车用户体验指数(UEI)

易观分析

新能源汽车 UEI

【文本检测与识别-白皮书】第一章:技术背景

合合技术团队

文字识别 文本 人工智能’

云备份服务CBR

创意时空

2022-09-07:给你一个由正整数组成的数组 nums 。 数字序列的 最大公约数 定义为序列中所有整数的共有约数中的最大整数。 例如,序列 [4,6,16] 的最大公约数是 2 。 数组的一个

福大大架构师每日一题

算法 rust 福大大

当代用电行为大赏:有人心疼电费,有人靠屋顶光伏“理财”

白洞计划

如何快速的部署一个静态页面到 Web3.0 上?5 分钟解密

掘金安东尼

前端 Web3.0 9月月更

MySQL不同隔离级别,都使用了什么锁?

Java全栈架构师

Java MySQL 数据库 程序员 程序人生

面试突击81:什么是跨域问题?如何解决?

王磊

Java 面试

构筑校园防线  “云资环”助力精准防控

神奇视野

给我一起学jdbc之sql注入

楠羽

JDBC 笔记 9月月更

纠删码在实时视频流中的应用丨Dev for Dev 专栏

声网

音视频 人工智能’

[Go WebSocket] 单房间的聊天室

HullQin

Go golang 后端 websocket 9月月更

设计模式的艺术 第四章简单工厂设计模式练习(使用简单工厂模式设计一个可以创建不同几何形状(如圆形、方形和三角形等)的绘图工具,每个图形都具有绘制draw()和擦除erase()两个方法,要求在绘制不支持的几何图形时,提示UnSupportedShape)

代廉洁

设计模式的艺术

Angular tsconfig.json 文件里的 paths 用途

Jerry Wang

typescript 前端开发 angular SAP UI5 9月月更

如何让百度搜索结果显示网站 logo

源字节1号

网站建设 网站开发

Go vs Python,我该选哪一门语言?

宇宙之一粟

Python 编程语言 Go 语言 9月月更

SQL 嵌套 N 层太长太难写怎么办?

jiangxl

微信小程序挖坑汇总

Shine

微信小程序

数据治理(十):Atlas案例演示

Lansonli

数据治理 9月月更

「趣学前端」来逛逛数字博物馆

叶一一

小程序 前端 9月月更

VUE 如何格式化数字

HoneyMoose

关于C语言结构体(struct),你不知道的用法?(初阶篇)

Albert Edison

指针 C语言 结构体 9月月更

SD-WAN网络编排原理

阿泽🧸

9月月更 网络编排

C++学习------clocale头文件的源码学习

桑榆

c++ 源码阅读 9月月更

NFT开发公司带你了解目前NFT开发属于什么状态

开源直播系统源码

区块链 NFT 数字藏品

每日一R「22」内存:堆与栈

Samson

学习笔记 ​Rust 9月月更

Unity 关于低版本是否可以引用高版本构建内容的可行性验证

CoderZ

C# dll Unity3D 9月月更

NEO FANTASY:回合制策略游戏在ACGN文化与GameFi中的新探索 09-07

鳄鱼视界

Java进阶(九)正则表达式

No Silver Bullet

Java 正则表达式 9月月更

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