正则表达式从小白到入门

2019-09-13 15:28:04 +08:00
 wsgzao

前言

正则表达式( Regular Expression,RegExp,regex )使用单个字符串来描述和匹配一系列符合某种句法规则的字符串。此概念来自形式化语言理论,最初由贝尔实验室实现。正则表达式最初在 Perl 中实现,它的推广得益于 UNIX 软件的流行,尤其是 SED,GREP 等。 现在许多编程语言都内置了正则表达式引擎,如 PERL、Python、JavaScript、Java、C++ 等。很多文本编辑器也支持正则表达式来进行检索和替换,如 Vim、Sublime Text、Visual Studio Code 等。正则表达式相关的学习文章网上也是一大推,本文主要记录正则表达式的入门教程和常用公式工具,方便大家活学活用。

正则表达式从小白到入门

更新历史

2019 年 09 月 12 日 - 初稿

阅读原文 - https://wsgzao.github.io/post/regex/

扩展阅读

正则表达式 30 分钟入门教程 - https://deerchao.net/tutorials/regex/regex.htm


为什么要正则表达式?

为什么需要正则表达式 - 王垠

学习 Unix 最开头,大家都学过正则表达式 (regexp)。可是有没有人考虑过我们为什么需要正则表达式?

正则表达式本来的初衷是用来从无结构的字符串中提取信息,殊不知这正好是 Unix 的缺陷所在。Unix 用无结构的字符串来表示数据,导致了诸多复杂的基于 regexp 的软件的诞生。sed, AWK, Perl, … 都是为了同样的目的来到这个世界上的。如果不是因为 Unix 用字符串来表示数据,我们就会拥有按数据结构类型的直接存储,而不需要折腾 regexp。正则表达式有它自己的价值(针对自然语言),但是我们其实不需要把它应用到程序语言和操作系统里面。

正则表达式本身用一个字符串来表示,这带来另外一些问题。因为正则表达式的本质不是字符串,而是一个数据结构。学过计算理论的人可能知道这个数据结构叫做 NFA ( nondeterministic finite automaton,非确定性有限自动机)。所有的数据结构应该由程序语言本身来表示,就像用 Java 构造一个对象用new ClassA("a") 一样。但是正则表达式强迫你把这个简单的构造函数调用写成一个字符串。所以在这个比方之下,你得写成 new ClassA(\"a\")。这样当你想要组合这些表达式的时候就发现,正则表达式几乎都是不可组合 (compose) 的。你几乎不可能不能把两个 regexp 的变量 A 和 B 安全拼接成一个,比如用 Java 的字符串拼接 A+B。因为你不知道这两个字符串拼在一起之后,那些稀奇古怪的符号会出现什么交叉反应,使得最后的识别的东西根本不是你想要的。

在正则表达式中,由于正则表达式本身的构造函数与数据本身合并到一起,我们不得不对某些 “特殊字符” 进行 escape。这些特殊字符,其实是用来描述 NFA 的记号,它们属于更高一层的语言。可是在正则表达式里,它们与 NFA 节点里的字符混为一谈。比如很简单的一个 block comment 的正则表达式,却要写成这个样子:

/\\\\\*(\[^\\\\\*\]|\[^/\])\*\\\\\*/

显然这样的表达式很容易出错。 如果我们用程序语言的表达式来构造这个表达式,它应该是这样:

(@... "/\*" (@\*(@!"\*/")) "\*/")

在这个我自己设计的 Scheme 表达式里,以 @开头的标识符都是构造函数。其中 @... 是构造 sequence,@* 是构造一个 zero-or-more 的匹配,@! 构造一个否定匹配。这个表达式是说:“以 / * 开头,接着零个或者多个不是 * / 的字符,最后接着一个 * /。这样一来清晰明了,什么表达式在什么 “层次” 都很清楚,不需要什么反斜杠 escape,而且这样的表达式可以 compose。比如:

(define reg1 (@... "/\*" (@\*(@!"\*/")) "\*/"))
(define reg2 (@+ "foo"))
(define reg3 (@= "b"))

定义这三个表达式之后,我们之后可以用像 (@... reg1 (@or reg2 reg3)) 这样的表达式来连接 3 个不同的表达式,构造出更大的表达式。这样的构造可以无限的扩展。从这里以及以往的经验,我总结出一个普遍适用的程序设计的教训:尽量不要把多个层次的语言 “压缩” 到一层。我们也看到正则表达式与 “Unix 哲学” 有很大关系。我没有考古,所以不知道孰先孰后,但是它们肯定有直接的因果关系。两者都是 Unix 复杂性的来源。

再来看取自 12306 网站的一段代码

// http://www.12306.cn/mormhweb/js/adKyfw.min.js
d = d.replace("'", "");
d = d.replace("%", "");
d = d.replace("#", "");
d = d.replace("&", "");
d = d.replace("*", "");
d = d.replace("(", "");
d = d.replace(")", "");
d = d.replace("@", "");
d = d.replace("`", "");
d = d.replace("/", "");
d = d.replace("\\", "");
d = d.replace(",", "");
d = d.replace(".", "");
d = d.replace("=", "");
d = d.replace("<", "");
d = d.replace(">", "");

上述代码是在过滤掉不合法的搜索字符(姑且不论客户端过滤是否安全), 我们可以用一行正则替换来实现相同的功能:

d = d.replace(/'%#\&\*\(\)@`\/\\,\.=<>/g, '');

正则表达式入门教程推荐

感谢作者 deerchao 从 2006 年开始更新至今,谢谢

正则表达式 30 分钟入门教程 - DeerChao

正则表达式 - 教程

Python RegEx

正则表达式在线工具

regexr

regex101

正则表达式测试工具(在线)

正则表达式在线测试

正则表达式基本语法

定义正则表达式的方式在不同的工具中可能有所差别,但正则表达式内容的语法是一致的。 正则表达式有三类语法结构:

  1. 串接(与操作)。相邻的字符默认为串接关系。例如 harttle 只能匹配 harttle, 不可匹配 hart
  2. 选择(或操作,|)。例如:harttle|serene 可以匹配 harttle 或者 serene。 选择的优先级级低于串接,因此很多情况下都可以省略括号。
  3. 数量(限定符)。最常见的数量限定符包括 +, ?, *,分别表示左侧的字符出现一次或更多,不出现或出现一次,不出现或出现任意次。例如 harttle? 可以匹配 harttlharttle
  4. 组合(括号,())。组合用来定义操作符的作用范围和优先级。例如 har(ttle)? 可以匹配 harttleharh(a|u)rttle 可以匹配 harttlehurttle

常用正则表达式

V2EX 不支持表格,跳过

参考文章

Wikipedia 正则表达式

MDN RegExp

正则表达式 30 分钟入门教程 - DeerChao

4394 次点击
所在节点    程序员
4 条回复
giveupAK47
2019-09-13 15:32:34 +08:00
用着搜着
pink123
2019-09-13 15:36:07 +08:00
Read it the doc and then you will know how to use it, and eventually you will master it.
It's not tough to master
xupefei
2019-09-13 19:37:35 +08:00
11206 那个例子我觉得是为了保证可读性,也为了以后扩展方便。相比之下,它对应的正则表达式一眼还看不懂,需要肉眼 parse ;想加一个符号的话还要先想想要不要 escape。
jellybool
2019-09-14 12:40:16 +08:00
正则表达式就是:从左往右

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/600638

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX