立即领取|华润集团、宁德核电、东风岚图等 20+ 标杆企业数字化人才培养实践案例 了解详情
写点什么

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

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

评论 1 条评论

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

一个银行客户经理的“变形记”

华为云开发者联盟

人工智能 金融科技

Docker私有化部署gitlab gitlab-runner

Leon

gitlab 持续集成 runner

深入浅出java虚拟机

AI乔治

Java 架构 性能优化 JVM JVM原理

LeetCode题解:225. 用队列实现栈,两个队列, 压入 - O(n), 弹出 - O(1),JavaScript,详细注释

Lee Chen

大前端 LeetCode

深兰科技的征途,AI的赛场与战场

脑极体

[翻译] Go Concurrency Patterns: Pipelines and cancellation[Go并发模式]

卓丁

channel pipeline

浅析LR.Net工作流引擎

Philips

敏捷开发 工作流 软件开发流程 开发工具

快来看看!AQS 和 CountDownLatch 有怎么样的关系?

程序员小航

Java AQS 源码阅读 CountDownLatch JUC

实战解读丨Linux下实现高并发socket最大连接数的配置方法

华为云开发者联盟

Linux TCP socket 高并发

北京城市副中心将试点法定数字货币

CECBC

数字货币 货币

CPU中的程序是怎么运行起来的

良知犹存

cpu

正在走进现实的“飞行汽车”,能否颠覆地面交通?

脑极体

Redis 数据同步机制--主从模式

是老郭啊

redis 主从配置 主从同步 redis主从 主从复制

或许是史上最好的AQS源码分析了,你确定要错过?!

InfoQ_d2212957090d

XSKY全新一代SDS一体机五大场景之存储+灾备

XSKY星辰天合

DB-Engines 9月数据库排名:ClickHouse一路猛冲,Redis坐稳第七

华章IT

MySQL 数据库 redis Clickhouse

鼓舞人心!主席支持数字经济!央行数字货币研究所为世界制定区块链相关国际标准

CECBC

区块链 金融

太赞了!华为工程师终于总结出了Linux归纳笔记,提供开放下载

小Q

云图说 | 一分钟带你扫盲云容器黑话

华为云开发者联盟

容器 节点 集群

使用amoeba实现mysql读写分离

小Q

Java MySQL 编程 程序员

架构师课作业 - 第十二周

Tulane

内存型数据库Redis,是如何实现持久化的?

Zhongger

redis

产业互联网成区块链与数字货币的分水岭

CECBC

区块链 数字货币 产业互联网

喷一喷坑爹的面向UI编程

架构师修行之路

用 Python 实现一个简易版的 Pong 游戏 (一)

Matrix Chan

Python Turtle Python游戏

为什么企业自主开发软件时,都会使用统一的模块化框架式开发平台?

Learun

敏捷开发 程序设计 开发工具 软件设计 技术方案

又踩Maven的两个坑

xiaoboey

maven Unknown lifecycle phase settings.xml 无效 PowerShell

深入Spring Security魔幻山谷-获取认证机制核心原理讲解

朱季谦

spring security

你问我答:容器平台改造后的安全是如何解决的?

BoCloud博云

云计算 容器 微服务 PaaS 博云

大数据管理:构建数据自己的“独门独院”

华为云开发者联盟

大数据 数据湖

【基础架构】不同场景下的数据存储技术,你用对了吗?

嘉为蓝鲸

网络 存储 系统 raid 磁盘挂载

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