2天时间,聊今年最热的 Agent、上下文工程、AI 产品创新等话题。2025 年最后一场~ 了解详情
写点什么

正则表达式(一):纠结的转义

  • 2011-01-21
  • 本文字数:2495 字

    阅读完需:约 8 分钟

【编者按】正则表达式一直是开发者工具箱中趁手的利器,但很多人对此仍是一知半解。这次 InfoQ 中文站有幸邀请到来自盛大创新院的余晟,开辟《正则表达式》的专栏,为读者讲解正则表达式的一些技巧和概念。他还是《精通正则表达式》和《技术领导之路》的译者。


用过正则表达式的人都知道,正则表达式中有一类叫做“元字符(meta-character)”的特殊符号,它们并不匹配自身对应的字符,而具有其他的含义。比如脱字符『^』表示“定位到字符串 / 行的开头”,加号『+』表示“之前的元素重现 1 次以上。如果需要匹配这些字符本身,需要用反斜线来转义,匹配『^』就应该用\^,匹配『+』就应该用\+。

看起来有点麻烦,但这样的元字符并不多:^$()*+?.[\{|

元字符

说明

举例

^

匹配整个字符串的起始位置,或者行的起始位置,如果在字符组内部,则表示排除型(negative)字符组

^Start

$

匹配整个字符串的结束位置,或者行的结束位置

End$

()

分组,提供反向引用 (gourp1) \1 或多选分支

(ab)+

* + ?

量词,限定之前元素出现的次数

a+ (ab)+

.

默认情况下匹配换行符之外的任意字符,在多行模式下可以匹配换行符

复制代码
\[

字符组的起始符号

[0-9]

\

反斜线用来表示转义序列,或去掉元字符的转义

\1

{

重现限定符的开始

{2, 6}

|

划分多选分支(括号没有出现时,可以想象括号出现在整个表达式最外层)

Tom|Jerry

你或许注意到了,这些元字符并不是“对称”出现的,比如与开方括号 [ 对应的闭方括号 ],与开花括号 { 对应的闭花括号 } ,这两个字符是否元字符,需要依据具体正则表达式的情况确定,我们以闭方括号] 的情况为例(}的情况与此类似):如果之前能找到与之对应的元字符开方括号 [,则] 作为元字符出现,否则,作为普通字符出现。

字符串 \ 表达式

[ab]]

ab]

a]

复制代码
ab\]

另外,因为方括号本身可以表示字符组『[0-9]』,所以在字符组内部的闭方括号在任何情况下都要转义,否则类似『[]]』的正则表达式会出现二义性,造成识别错误。

如果需要匹配方括号内(包括方括号),至少包含一个字符的字符串(比如 [text]),所用的正则表达式就应该是:『\[[^\]]+]』。

看明白了吗?『\[』匹配开方括号,然后用一个排除型字符组匹配“除闭方括号 ] 之外的任意字符(注意,在字符组内部,闭方括号 ] 一定需要转义),用『+』表示它至少要出现一次以上,最后用一个『]』匹配闭方括号。

下面用代码来验证,以 python 为例:

import re #为使用正则表达式,必须首先导入 re

>>> re.search(’^\[[^\]]+]$’, ‘[abcdefg]’) #进行数据验证时,在表达式首尾加上 ^ 和 $ 是好习惯

<_sre.SRE_Match object at 0x7ff3bc5e75e0>

>>> re.search(’^\[[^\]]+]$’, ‘[]’)

>>>

看来确实没有问题,下面用 Java 试试。直接调用 Java 中的 _string_.matches(regex) 方法,观察返回的 boolean 值:

“[]”.matches("^\[[^\]]+]$")

但是却出现了编译错误:invalid escape sequence。这是为什么呢?在 Python 中我们并没有使用 raw string(如果使用 raw string,就应该用 r"^\[[^\]]+]$"),一切正常,可是在 Java 中为什么会出错呢?

要回答这个问题,就得分清转义的层次和规则。如果你留心观察就会发现,上面我们讲的都是“正则表达式的转义”,比如『\[[^\]]+]』是正确转义的正则表达式。仅仅用做正则表达式,它是绝对没有问题的,但它“不仅仅”是正则表达式,而是“字符串形式给出的正则表达式”——注意到了吗?在表达式两端,各有一个双引号。

回忆一下 Java 中字符串(String)的规则,其中转义序列(escape sequence)用来表示特殊字符,比如\n 表示换行符,\t 表示制表符,而\[并不是 Java 能识别的转义序列,当然要出错了。为了表示“正则表达式中的\[”,我们传递给 Pattern.compile() 的字符串必须正确表示\[——在字符串中,[ 是不需要转义的,而 \ 是需要转义的,所以在字符串中,应该写做 \\[。

总结一下:

字符串的表现层

\\[

字符串的概念层

\[

正则表达式的表现层

\[

正则表达式的概念层

[(非元字符)

理解了这一点,就不难理解为什么正则表达式的转义序列在正则表达式中要写两个反斜线了,比如 \+ 要写成 \\+ 。但是 \n 之类的有点特殊,无论你写成 \n 或是 \\n ,结果都是一样,\t 之类的情况与此类似。

字符串的表现层

\\n

\n

字符串的概念层

\n

换行符

正则表达式的表现层

\[

换行符

正则表达式的概念层

换行符

换行符

如果字符串中表示反斜线字符本身(不是用来转义的符号),则需要在正则表达式中写四个反斜线字符。

“\”.matches("\\\\"); //true

字符串的表现层

\\\\

字符串的概念层

\\

正则表达式的表现层

\\

正则表达式的概念层

\(非元字符)

看起来,转义问题似乎就是这样,想明白了也很简单。不过,如果你记忆力比较好,估计会问:为什么在Python 中写\[不会报错,而Java 中会报错?这确实是个好问题,所以我们把它当成本文的结束。

照道理说,各种语言的转义规则都一样:\n 表示换行符,\t 表示制表符…… 事实也确实如此,只是Python 对字符串的处理更复杂一些:如果一个转义序列不能识别,会直接原样保存到字符串中。也就是说,Python 遇到无法识别字符串中的\[,不会报错,而是将它原样“转交”给字符串:

字符串的表现层

\[

\\[

字符串的概念层

\[

\[

正则表达式的表现层

\[

\[

正则表达式的概念层

[(非元字符)

[(非元字符)

“无法识别的转义序列直接转交字符串”的做法不只 Python 有,PHP 也会这样处理,但是我并不推荐这样使用,因为它往往会令不理解这特性的人困惑,正则表达式对应的字符串中出现\[如何不会报错?\[和\\[为什么竟然是一样的效果?

最好的办法或许还是统一表示法,都写成\\[,既方便与其它语言兼容,也方便大家阅读和理解。

关于作者

余晟,程序员,曾任抓虾网高级顾问,现就职于盛大创新院,感兴趣的方向包括搜索和分布式算法等。翻译爱好者,译有《精通正则表达式》(第三版)和《技术领导之路》,目前正在写作《正则表达式傻瓜书》(暂定名),希望为国内开发同行贡献一本实用的正则表达式教程。


感谢张凯峰对本文的策划及审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。

2011-01-21 00:0025537

评论

发布
暂无评论
发现更多内容

如何基于 Docker 快速搭建 Springboot + Mysql + Redis 项目

秦怀杂货店

Java redis Docker springboot redis sentinel

读《思辨与立场》-04自我理解

wood

28天写作 批判性思维 思辨与立场

数据库牛人是如何进行SQL优化的?

Ethereal

数据库 sql SQL优化

[Pulsar] Pulsar Resources介绍

Zike Yang

Apache Pulsar 12月日更

当我们在聊高可用时,我们其实在聊什么?

xcbeyond

28天写作 12月日更

Enum Parse 中的坑

喵叔

28天写作 12月日更

Dubbo框架学习笔记四

风翱

dubbo 12月日更

Go+ XML 编码和解码处理教程(5.5)

liuzhen007

28天写作 12月日更

All in one:如何搭建端到端可观测体系

阿里巴巴云原生

阿里云 云原生 可观测

什么是Kubernetes?Kubernetes是怎样工作的?

Ethereal

云计算 Kubernetes

前端开发:Vue中获取input输入框值的方法

三掌柜

28天写作 28 12月日更 12月

100行代码让您学会JavaScript原生的Proxy设计模式

汪子熙

JavaScript 设计模式 代理模式 28天写作 12月日更

5分钟认识802.11标准,言简意赅!

Ethereal

网络技术 无线技术 网络技术联盟站 802.11

JavaScript操作 DOM 的这些事件基础

你好bk

JavaScript 前端 DOM BOM WebApi

数据分析流程浅析

圣迪

数据分析 数据 CRISP-DM

明道云对接小鹅通,沉淀内容付费平台数据

明道云

开源轻量级 IM 框架 MobileIMSDK v6.1.2 发布

JackJiang

TCP websocket 即时通讯 IM MobileIMSDK

TCP/IP的底层队列

程序员历小冰

网络 TCP/IP 28天写作 12月日更

【docker 总结】第四篇 - 镜像和容器操作

Brave

Docker 12月日更

DotNetCore 开发工具箱之调度小能手——Coravel

为自己带盐

dotnet 28天写作 12月日更 Coravel

Spring Boot Serverless 实战系列“架构篇” | 光速入门函数计算

阿里巴巴云原生

阿里云 Serverless 架构 云原生 函数计算

Python爬虫120例之案例58,手机APP爬虫,“武器库”的准备and皮皮虾APP的测试

梦想橡皮擦

12月日更

如何在 AWS 云中从 Amazon EC2 启动 RHEL 8?

Ethereal

云计算 AWS RHEL 8 Amazon EC2

山的那一边,是什么?(16/28)

赵新龙

28天写作

React进阶(七):props 属性介绍

No Silver Bullet

React 12月日更 props

Apache Log4j2,RASP 防御优势及原理

阿里巴巴云原生

阿里云 云原生 漏洞 可观测 Apache Log4j2

Android aapt 在 Mac 和 Windows 上使用方法小结

阿策小和尚

28天写作 Android 小菜鸟 12月日更

管理中的推理事件随笔

搬砖的周狮傅

随笔杂谈 推理

小程序的发布【小程序专题3】

坚果

小程序 28天写作 12月日更

实用机器学习笔记十六:循环神经网络

打工人!

深度学习 学习笔记 循环神经网络 机器学习算法 12月日更

明道云荣获2021亚洲最佳职场“最佳创新奖”荣誉!

明道云

正则表达式(一):纠结的转义_Java_余晟_InfoQ精选文章