V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
wsgzao
V2EX  ›  程序员

正则表达式从小白到入门

  •  
  •   wsgzao ·
    wsgzao · 2019-09-13 15:28:04 +08:00 · 4404 次点击
    这是一个创建于 1903 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前言

    正则表达式( 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

    4 条回复    2019-09-14 12:40:16 +08:00
    giveupAK47
        1
    giveupAK47  
       2019-09-13 15:32:34 +08:00 via iPhone
    用着搜着
    pink123
        2
    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
        3
    xupefei  
       2019-09-13 19:37:35 +08:00 via iPhone
    11206 那个例子我觉得是为了保证可读性,也为了以后扩展方便。相比之下,它对应的正则表达式一眼还看不懂,需要肉眼 parse ;想加一个符号的话还要先想想要不要 escape。
    jellybool
        4
    jellybool  
       2019-09-14 12:40:16 +08:00 via Android
    正则表达式就是:从左往右
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3107 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 14:15 · PVG 22:15 · LAX 06:15 · JFK 09:15
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.