news 2026/8/10 8:33:02

正则指引——匹配模式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
正则指引——匹配模式

匹配模式

    • 1、不区分大小写模式与模式的指定方式
    • 2、单行模式
    • 3、多行模式
    • 4、注释模式
    • 5、补充
      • 5.1、更多的模式
      • 5.2、修饰符的作用范围
      • 5.3、失效修饰符
      • 5.4、模式与反向引用
      • 5.5、冲突策略
      • 5.6、哪种方式更好

所谓匹配模式(match mode)​,指的是匹配时遵循的规则。设置特定的模式,可能会改变对正则表达式的识别,也可能会改变正则表达式中字符的匹配规定。常用的匹配模式一共有4种:

  • 不区分大小写模式
  • 单行模式
  • 多行模式
  • 注释模式

1、不区分大小写模式与模式的指定方式

通常,用户关心的只是文本的意义,而不是它的具体形式。比如单词the,在句子中写作the,在句子开头写作The,还可能为了强调写作THE,可是用户不管这些,只希望找到所有的the。为达到这个目的,可以使用第1章介绍的字符组,写作[tT]​[hH]​[eE],这样做确实没错,但是如果单词长一些,写起来就很麻烦了,比如tomorrow就要写成[tT]​[oO]​[mM]​[oO][rR]​[rR]​[oO]​[wW]。更重要的是,这样的表达式不够直观,读代码的人很难明白[tT]​[oO]​[mM]​[oO]​[rR]​[rR]​[oO]​[wW]要匹配的是tomorrow。

为解决这种问题,正则表达式提供了不区分大小写的匹配模式,指定此模式之后,在正则表达式中可以直接写the,就可以匹配the、The、THE等各种大小写形式的the,tomorrow也可以匹配各种形式的tomorrow,大大降低了理解的难度。

在看这个模式的应用实例之前,必须首先了解模式的指定方式。通常,有两种办法指定匹配模式:

  • 以模式修饰符指定
  • 以预定义的常量作为特殊参数传入来指定

模式修饰符即模式名称对应的单个字符,使用时将其填入特定结构(?modifier)中(其中的modifier为模式修饰符)​,嵌在正则表达式的开头。

比如不区分大小写的匹配模式对应的模式修饰符是i(case Insensitive)​,对the指定此模式,完整的正则表达式就是(?i)the

这个表达式几乎可以原封不动地用在任何语言中(只有JavaScript是例外,JavaScript不支持模式修饰符(?modifier)的记法)​,其意义都是相同的:对the采用不区分大小写的模式。Python中的结果下例所示。

用模式修饰符指定不区分大小写模式

另一种指定模式的方式是使用预定义的常量作为参数,传入正则函数。在Python中不区分大小写的预定义常量是Re类的静态成员re.IGNORECASE(一般来说,它都是某个类的静态成员)​,在Java中它属于Pattern类,在.NET中属于RegexOptions,在Ruby中属于Regexp。PHP和JavaScript是例外,它们的做法(其实在Ruby中也可以这么做)是在正则表达式末尾的分隔符(delimeter)之后加上模式对应的字母(比如不区分大小写模式对应的字母是i,则添加字母i)​。

下表列出了常用语言中的写法。Golang的语法更加奇怪,似乎完全不支持这种做法,只能在表达式中以修饰符来指定匹配模式。各语言中用预定义常量指定不区分大小写模式的例子参见下例。


各语言中用预定义常量指定不区分大小写模式


比较两种指定形式,模式修饰符较为通用,因为在各种语言中写法基本相同,而预定义常量在不同语言中写法不同。不过,两种形式的效果是相同的:无论以哪种方式,只要指定了不区分大小写模式,正则表达式在匹配时,就不会区分同一个字母的大小写形式((?i)the和(?i)THE是完全等价的)​。

之前看到过匹配HTML中tag的例子,比如匹配超链接tag的正则表达式、匹配图片tag及网页标题tag的正则表达式​,虽然XHTML规范推荐tag名都用小写字母,但类似<IMG>的tag也很有可能出现,为同时兼容大写字母,可以使用不区分大小写模式,这样比用字符组详细列出字母的大小写形式简单很多,如表所示。

2、单行模式

元字符点号.几乎能匹配任何字符,唯有换行符\n是例外。但是,有时候确实需要匹配“任何字符”​,比如在处理HTML源代码时,经常会遇到跨越多行的脚本代码。


正则文档里一般都会说明“点号.不能匹配换行符”​,不过许多人并不习惯仔细阅读文档,所以认为点号.能匹配任何字符,当然也就包括换行符,所以直接的想法是用<script\s.*?</script>来匹配。

因为这段JavaScript代码中出现了换行符,所以.*?的匹配最多只能延伸到第一行末尾。之前提到过,可以用[\s\S]之类的字符组匹配“任意字符”​,所以正则表达式<script\s[\s\S]*?</script>能解决问题。

不过对大多数人来说,点号更自然,也更简洁,所以正则表达式提供了单行模式。在这种模式下,所有文本似乎只在一行里,换行符是这一行中的“普通字符”​,所以可以由点号.匹配。

单行模式对应的模式修饰符是s(Single line)​,所以如果用模式修饰符,可以在表达式的开头用(?s)指定,因此上面的表达式也可以改写为(?s)<script\s.*?</script>。使用预定义常量的写法见下表。



你可能注意到了,单行模式在不同语言中的称呼很不一样,甚至在同一门语言中,也可能有不同的记法(比如在Python中)​。比如在Java和Python中叫作DOTALL(也就是点号通配)​,这个名字确实更高明(因为常用的模式中还包含“多行模式”​,它和“单行模式”没什么联系,但是这两个名字确实很迷惑人)​。不过,​“单行模式”成了约定俗成的称呼(通用的模式修饰符是s,它难以联系上DOTALL)​,所以本书还是沿用“单行模式”的说法。

比较奇怪的是Ruby的预定义常量Multiline,它的意思是“多行”​,而且它使用的模式修饰符也是m。这确实让人费解,或许本意是“使用点号.的正则表达式可以跨越多行”​?不管怎样,请一定记住,Ruby中的“多行模式”实际上是常说的“单行模式”​。

如果不想使用“点号+单行模式”的组合(比如JavaScript完全不支持这种模式,想用也没办法)​,也可以使用[\s\S]之类的字符组,它的确可以匹配任何字符,只是许多人并不习惯这样的写法,​“点号+单行模式”的组合看起来更顺眼一些。

3、多行模式

“多行模式”听起来是与“单行模式”对应的,其实这两个模式没有任何联系。

  • 单行模式影响的是点号的匹配规则:在默认模式下,点号.可以匹配除换行符之外的任何字符,在单行模式下,点号.可以匹配包括换行符在内的任何字符;
  • 多行模式影响的是^$的匹配规则:在默认模式下,^和$匹配的是整个字符串的起始位置和结束位置,但在多行模式下,它们也能匹配字符串内部某一行文本的起始位置和结束位置。

假设,需要找到下面文本中所有数字字符开头的行。


解决这个问题,需要定位到每行的起始位置,尝试匹配一个数字字符,如果成功,则匹配之后的整行文本。多行模式的模式修饰符是m(Multiline)​,所以在表达式的开头用(?m)指定多行模式,这样^可以定位到字符串内部每一行的起始位置;匹配数字字符的表达式是\d,因为没有指定单行模式,点号.不能匹配换行符,.*可以匹配“之后的整行文本”​,整个表达式就是(?m)^\d.*,示例见下例。

用模式修饰符指定多行模式


还可以利用多行模式下的$,给每一行的末尾添加句号,如下例所示。

在多行模式下,给行末添加句号


上面的表达式几乎是任何语言中都“通用”的,唯有JavaScript是例外,因为它不支持用模式修饰符指定模式,但是可以使用预定义常量来指定多行模式。在下表中列出了常用语言中预定义常量的写法。


在各种语言中,多行模式对应预定义常量的写法比较统一,都是multiline。值得一提的是Ruby,它默认就采用多行模式,^$在任何情况下都能匹配文本内部的行起始/结束位置。如果要在Ruby中“摆脱”多行模式,只能用\A替代^,用\Z替代$

4、注释模式

有时,用到的正则表达式可能非常复杂,不但难以编写和阅读,也难以维护。如果正则表达式也像程序源代码一样,可以添加注释,阅读和维护起来就容易多了。

为解决这个问题,许多语言支持使用(?#comment)的记法添加注释,comment就是注释的内容。所以,上面的表达式^\d.*?$就可以写成这样:


.NET、Python、Ruby、PHP、Objective-C都支持这种记法,Java、JavaScript、Golang不支持。不过,还有一种注释的写法是各种语言都支持的,就是使用注释模式,此时,正则表达式对应的字符串可以跨越很多行。注释模式的代码见下例。

注释模式

在注释模式下,正则表达式内部的空白字符都被忽略(一般来说,主要是ASCII编码中的空白字符,Unicode编码中的空白字符情况不定)​,注释则以#comment的形式添加在正则表达式内部,每一条注释从#开始,到行末结束。许多文档中都用这种模式来解释复杂的表达式,并且会使用缩进表示层级结构,这样更加方便阅读和维护。比如匹配日期的正则表达式((?x)(\d{4})-(\d{2})-(\d{2})),在注释模式下可以就这样像下例这样展开。

在注释模式下展开复杂正则表达式

注释模式对应的模式修饰符是x(extended mode,扩展模式,但更常见的写法是free-spacing mode,宽松格式模式)​。下表介绍了各语言中注释模式的预定义常量。


你可能注意到了,实力同时指定了两种模式:多行模式和注释模式。注释模式的x与多行模式的模式修饰符m,合写作(?mx)。如果需要同时使用多种模式,只要在(?modifier)中将模式修饰符排列起来就可以了。

如果希望同时指定多行模式和注释模式,使用预定义常量该怎么做?答案是,使用位运算符|。通常来说,匹配模式对应的预定义常量都是int类型,所以多个值进行按位与的结果,并不会彼此干扰。比如在Java中,对应的写法就是Pattern.COMMENTS | Pattern.MULTILINE,在.NET、Ruby和Python中也可以这样。

如果是PHP和JavaScript,则在结束的分隔符之后直接并列模式对应的修饰符,比如/regex/mx,这种写法在Ruby中也行得通。

5、补充

5.1、更多的模式

上面提到的4种常用模式是各种语言中通用的,但是匹配模式并不只有这4种,不同的语言提供了不同的模式,它们一般都在预定义常量中,而且不一定有对应的模式修饰符。

比如Java的Pattern还包含其他模式(仅列举两种模式,更多的请参看文档)​。

  • Pattern.UNIX_LINES:在此模式下,^.$识别的“行终止符”只有\n,而不能是其他字符(比如\r\n)​,它对应的模式修饰符是d
  • Pattern.CANON_EQ:在此模式下,字符的“相等”规则更加灵活,Unicode字符a\u030A与\u00E5也是“相等”的(都是å,也就是拉丁字母a加上分音符,只是前者用两个字符组合,后者用单个字符)​,此模式可能对性能有很大影响,而且没有对应的模式修饰符。

Python的re也包含了其他模式(仅列举两种模式,更多的请参阅文档)​。

  • re.Ure.UNICODE:在此模式下,\w、\d、\s等字符组简记法的匹配规则会发生改变,比如\w能匹配Unicode中的“单词字符”​,包括中文字符,\d也能匹配1、2之类的全角数字字符,它有对应的模式修饰符u。
  • re.Are.ASCII:因为在Python 3以上的版本中,正则表达式默认采用Unicode匹配规则,如果希望让\d、\w等字符组简记法恢复到ASCII匹配规则,可以使用此模式,它有对应的模式修饰符a。

在.NET和PHP中,也有其他模式可用。不过一般来说,本章介绍的4种模式最为常用,在其他模式中,只有涉及Unicode或者ASCII的模式使用较多,因为它们影响了字符组简记法\d、\s、\w的匹配规则。关于每种语言可用模式的具体信息,请参考该语言对应的章节或文档。

5.2、修饰符的作用范围

常见的模式修饰符是(?modifier)形式的,它表示“从现在开始使用某个模式”​。

  • 通常的做法是将它写在正则表达式的最开头,表示“整个正则表达式都指定此模式”​;
  • 如果它出现在正则表达式当中,则表示此模式从这里开始生效;​
  • 如果模式修饰符出现在某个括号内—比如((?modifier)…)—那么它的作用范围只限于括号内部,此模式也可以记为(?modifier:…)

模式修饰符的作用范围见下表所示。

模式修饰符对正则表达式的操控性更强,因为预定义常量指定的匹配模式是对整个表达式生效的。

5.3、失效修饰符

(?modifier)指定了匹配模式开始作用的范围,在正则表达式中,另有一类失效修饰符,它用来“终止”某种模式的作用范围,其形式是(?-modifier),类似(?modifier),只是问号?之后多了一个减号-,表示“取消模式”​,也就是某个模式生效到此处为止。

假设,一段文本中包含了许多单词,其中以bar结尾的有foobar、zeebar等,而且大小写不定。

现在希望找出所有这样的foobar和zeebar:不论foo和zee的大小写如何,只要结尾是大写的BAR就可以。FooBAR、ZEEBAR、zeeBAR都符合要求,Foobar、zooBar、feeBAR等则要排除。

你可能会觉得这很简单,要找到的单词,前面部分可能是zee或是foo,不区分大小写则用字符组把大小写形式都列出来即可,后面必定是BAR,所以正则表达式是[fFzZ]​[oOeE][oOeE]BAR。可是,这个表达式行不通,因为它可以匹配feeBAR,而这并不是我们需要的。

真正要做的,其实是准确划定不区分大小写模式的作用范围即可:匹配前面部分的表达式是(foo|zee),同时必须使用不区分大小写模式;匹配后面部分的BAR则必须停止使用不区分大小写模式。

要满足这个要求,可以使用上一节介绍的((?i)foo|zee)BAR,也可以使用失效修饰符,将表达式写作(?i)(foo|zee)(?-i)BAR,代码见下例。因为Python不支持这种写法,所以使用了Ruby​。

不区分大小写模式与反向引用

一般来说,只要语言或工具支持模式修饰符(?modifier),都可以支持失效修饰符(?-modifier),两者配合使用,可以更精确地设定模式的作用范围。不过,Python和JavaScript并不支持(?-modifier)的写法。Golang的文档里虽然没有明确指出支持,但实际代码测试可以支持。

5.4、模式与反向引用

反向引用前面介绍过,它引用之前的表达式匹配的文本。如果之前的表达式使用了某种模式,引用时是否会继承这种模式呢?下面看看实际情况,因为在Python中无法限定模式的作用范围,现在以Java为例,代码见下例。

不区分大小写模式与反向引用


在表达式((?i)abc)\1中,\1引用的是(?i)abc能匹配的文本(在这个例子里就是abc)​,因为\1不在区分大小写模式的作用范围内,所以无法匹配ABC;而在表达式(?i)(abc)\1中,\1处在区分大小写模式的作用范围内,所以可以成功匹配ABC。

在下例所示的两个表达式中,\1引用的是之前a.匹配的文本,在单行模式下.可以匹配换行符;所以a.匹配的是字符a和换行符\n,因此\1也可以匹配字符a和换行符\n

单行模式与反向引用


在下例的两个表达式中,\1引用的是之前^a匹配的文本,^a只能匹配行开头的a;但是无论\1是否处在多行模式的作用范围内,它能匹配的不只有行开头的a,而是任何位置的字符a,这一点值得注意。

多行模式与反向引用

5.5、冲突策略

使用(?-modifier)可以终止某个模式的作用范围;但是,模式也可以通过预定义常量来指定,它是对整个表达式生效的。这时候,就可能产生冲突。

如果在正则表达式中使用(?-i)取消不区分大小写的匹配模式,又使用了预定义常量(比如Java中的Pattern.CASE_INSENSITIVE)指定整个表达式采用不区分大小写的模式,这时候情况会是怎样呢?从下例可以看到。

冲突策略

可以看到,模式修饰符具有更高的优先级。严格地说,如果用预定义常量指定了整个正则表达式采用某种模式,同时正则表达式内部使用了关闭该模式的失效修饰符,则失效修饰符之后的部分,都不受预定义常量所指定模式的影响(失效修饰符之前的部分则不受影响)​。并不是每种语言的文档都会针对这种情况做详细讲解,但大家的原则一致。下例给出了具体的例子。

更详细的例子

5.6、哪种方式更好

模式修饰符和预定义常量都可以指定匹配模式,哪种方式更好?这个问题没有标准答案。

用模式修饰符指定的好处之一是简洁,因为通用的模式修饰符就只有ixsm,虽然不那么直观,但习惯之后并不难理解;而且,这些记法在各语言中都是通用的;再者,模式修饰符还可以用(?i)(?-i)来精确控制模式的作用范围,这是预定义常量做不到的;另外,许多正则处理函数只接受字符串形式的正则表达式,此时只能以模式修饰符来标识模式。

使用预定义常量的好处在于,它很形象—“单行模式”​“多行模式”的说法确实很让人困惑,尤其它们竟然可以同时使用,互不干扰,而看字面意思分明是互相矛盾的;而且,一些模式并没有对应的修饰符,比如Java中的Pattern.LITERAL(消除所有元字符的特殊含义)​、.NET中的RegexOptions.ECMAScript(字符组简记法采用ASCII匹配规则)​。

我的建议是,熟练掌握常用的模式,在能够使用模式修饰符的地方,尽量使用修饰符。毕竟,无论使用哪种编程语言,(?i)是熟悉正则表达式的人都能看懂的,但是不是每个人都能记得的,Java中的Pattern.DOTALL等价于.NET中的RegexOptions.Singleline,也不是每个人都能记得,Java中的Pattern.CASE_INSENSITIVE等价于RegexOptions.IgnoreCase,因此,理解起来总是要多点周折。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/10 8:31:44

自定义内存分配器性能优化与实现解析

1. 自定义分配器性能对比&#xff1a;从原理到实战的深度解析内存分配器作为系统底层的核心组件&#xff0c;其性能直接影响应用程序的整体效率。当标准库提供的默认分配器无法满足特定场景需求时&#xff0c;开发自定义分配器成为性能优化的关键手段。本文将深入探讨四种典型自…

作者头像 李华
网站建设 2026/8/10 8:31:30

Flask+微信小程序构建三端在线考试系统实践

1. 项目概述&#xff1a;三端在线学习考试平台的设计初衷去年接手一个教育机构的数字化转型项目时&#xff0c;我深刻体会到传统线下考试的痛点&#xff1a;纸质试卷印刷成本高、人工阅卷效率低、成绩统计周期长。这促使我用Flask微信小程序技术栈开发了一套支持PC网页端、移动…

作者头像 李华
网站建设 2026/8/10 8:30:58

Django + MySQL + Vue 实现在线租房平台

Django MySQL Vue 是做在线租房平台非常成熟的全栈组合&#xff1a;Django REST Framework 写 API、MySQL 存业务数据、Vue 3 做前端 SPA&#xff0c;前后端完全分离&#xff0c;开发、测试、部署都能解耦。下面给你一套能直接落地的方案&#xff0c;从架构到表结构到开发步骤…

作者头像 李华
网站建设 2026/8/10 8:28:40

C++20概念:编译期类型契约,提升泛型编程安全与表达力

1. 项目概述&#xff1a;从“差不多就行”到“必须这样”的编译期契约 如果你写过一段时间的C模板&#xff0c;尤其是泛型编程&#xff0c;肯定经历过这种场景&#xff1a;你写了一个漂亮的 template<typename T> 函数&#xff0c;期望 T 是一个可以比较大小的类型。…

作者头像 李华
网站建设 2026/8/10 8:28:07

Python+Django构建家庭光伏发电监控系统实践

1. 项目概述&#xff1a;光伏发电系统的数字化管理方案 这个基于PythonDjango的家庭光伏发电管理系统&#xff0c;本质上是一个面向分布式能源场景的数据采集与监控平台。我在实际部署中发现&#xff0c;现代家庭光伏系统每天产生的运行参数&#xff08;发电量、逆变器状态、环…

作者头像 李华
网站建设 2026/8/10 8:27:13

济阳网站建设哪家好?揭秘本地企业如何打造高转化、低成本的专业网站

济阳网站建设哪家好?这应该是每一位正在济南济阳区打拼的老板或者企业负责人在深夜辗转反侧时,脑海中盘旋最多的一个问号。在这个互联网流量红利逐渐见顶,私域流量和精准获客成为生存关键的大背景下,一个网站不再仅仅是一张大名片,它是你24小时不睡觉的销售经理,是你品牌…

作者头像 李华