V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
MXXXXXS
V2EX  ›  分享创造

组内技术分享, 准备了一篇正则的介绍文章

  •  
  •   MXXXXXS · 2022-06-28 23:28:17 +08:00 · 3098 次点击
    这是一个创建于 639 天前的主题,其中的信息可能已经有所发展或是发生改变。

    低创, 求放过, 优点是比较 简洁

    只要有人不知道就不算火星(≧∇≦)ノ

    原地址: https://github.com/MXXXXXS/Introduction-to-JS-Regex

    如有资料侵权, 请立即联系本人删除, 可以在 Github 里发 issue

    JS 正则表达式的介绍

    什么是正则表达式(Regular Expressions)

    正则表达式是对文本模式的描述

    在自然语言中,1, 2, 3 被称为 数字, 水, 火, 风 被称为 物质, 数字, 物质是对事物的描述

    类似的,在一段文本中,a, b, c可以称为 单词字符, 文本 1, 2, 3可以称为 数字字符

    单词字符, 数字字符 这些概念用符号表示,这些符号就是正则表达式

    正则表达式是一种语言, 专用于模式匹配领域的语言(DSL, Domain-specific language)

    为什么叫“JS 正则表达式的介绍”, 为什么要加上“JS”

    正则表达式是为了解决模式匹配这个问题产生的一种方法

    其有很多种实现, 不同编程语言有不同的正则表达式实现,表达式的语法也有差异

    这里讨论的是 JS 里的正则表达式语法

    更多类型的正则表达式细节可以参考这里

    测试表达式的工具

    为了方便练习测试编写正则表达式,这里推荐一个工具网站 regexr

    常见的表达式

    像上面提到的 单词字符 对应的符号是 \w, 数字字符 对应的是\d

    还有很多其他的符号,下面来看一些常见的表达式

    单个字符

    单个的字符本身就是一种模式

    /Regex/
    

    会匹配

    "Regex"
    

    一类字符

    要匹配所有的单词字符

    /\w/
    

    会匹配

    "a", "b", "_", "0"
    

    注意/\w/里的\代表转义,/w/会匹配 "w"

    /\W//\w/的反面(非单词字符), /\D//\d/的反面(非数字字符)

    多者之一

    使用[]

    使用[]来包含多个模式,会匹配多个模式中的一个

    /[abc]/
    

    会匹配

    "a", "b", "c"
    

    但不会匹配除这三个之外的别的字符

    范围

    []里,-具有表示范围的功能

    上方的表达式还可以写成

    /[a-c]/
    

    不匹配

    []里,^放置在开头表示不匹配

    /[^a-c]/
    

    表示不匹配a, b, c, 或表示匹配除a, b, c之外的别的字符

    多组模式之一

    使用()来表示一组模式

    /[(a-c)(e-g)]/
    

    匹配

    "a", "b", "c"
    "e", "f", "g"
    

    使用|

    |类似“或”的含义,上面的例子也可以写成

    /a|b|c/
    /[a-c]|[e-g]/
    

    数量指定

    ?代表某个模式的 0 或 1 次

    +代表某个模式的 1 或多次

    *代表某个模式的 0 或多次

    /a*/
    

    匹配

    "", "a", "aa", "aaa"
    

    {n}指定具体的数量

    /a{2}/
    

    只匹配

    "aa"
    

    {m,n}指定范围

    /a{1,3}/
    

    匹配

    "a", "aa", "aaa"
    

    {m,}会至少匹配 m 个

    /a{1,}/
    

    匹配

    "a", "aa", "aaa", "aaaa"
    

    范围匹配时,尽量多匹配还是少匹配(贪婪模式和惰性模式)

    比如

    "aaaaa"
    

    使用

    /a{1,3}/
    

    会匹配到

    "aaa"
    

    这是默认的“尽量多匹配”的模式(贪婪模式)

    如果加上?

    /a{1,3}?/
    

    会匹配到

    "a"
    

    这是“尽量少匹配”的模式(惰性模式)

    位置

    字符之间的间隔被称为"位置", 有一些正则符号可以用来代表位置, 用来辅助匹配

    行首, 行尾

    比如

    "22.33"
    

    要匹配小数点前面的部分, 可以用

    /^\d*/
    

    来匹配, 其中^代表了行首的位置

    要匹配小数点后面的部分, 可以用

    /\d*$/
    

    来匹配, 其中$代表了行末的位置

    单词边界

    单词字符非单词字符(比如标点符号)之间的间隔用\b表示

    比如要统一日期的表示, 将2022.6.28, 2022/6/28都统一成2022-6-28

    '2022.6.28'.split(/\b\W*\b/).join('-')
    '2022/6/28'.split(/\b\W*\b/).join('-')
    

    Lookahead and lookbehind, Positive and Negative

    Lookahead lookbehind
    Positive (?=p) (?<=p)
    Negative (?!p) (?<!p)

    其中p代表一个模式

    "Positive Lookahead"指的是匹配p前面的位置, "Positive lookbehind"指的是匹配p后面的位置

    "Negative Lookahead"指的是不匹配p前面的位置, "Negative lookbehind"指的是不匹配p后面的位置

    举例

    /Task\d (?=(done))/g
    

    会匹配完成的 Task(后面跟着"done")

    image-20220628221120638

    要匹配没有完成的项, 使用

    /Task\d (?!(done))/g
    

    image-20220628221352258

    Flags

    上面的表达式出现了g, 这是一个用于控制表达式行为的标志位

    参考Advanced searching with flags

    Flag Description Corresponding property
    d Generate indices for substring matches. RegExp.prototype.hasIndices
    g Global search. RegExp.prototype.global
    i Case-insensitive search. RegExp.prototype.ignoreCase
    m Multi-line search. RegExp.prototype.multiline
    s Allows . to match newline characters. RegExp.prototype.dotAll
    u "unicode"; treat a pattern as a sequence of unicode code points. RegExp.prototype.unicode
    y Perform a "sticky" search that matches starting at the current position in the target string. See sticky. RegExp.prototype.sticky

    表达式应用

    格式校验

    这个很常见, 校验各种格式

    比如 Meflow 里金额字段的识别

    image-20220628222702335

    以及相关测试用例

    image-20220628222817023

    内容提取

    image-20220628223116546

    其中/\p{sc=Han}/代表一个汉字, 参考Unicode property escapes

    文本替换

    String.prototype.replace()非常强大, 这里特指其第二个参数是一个replacerFunction的时候

    比如一个获取某篇博客文章的某个评论的 api 格式类似

    Get /api/:blogId/comments/:commentId
    

    要构造一个请求路径, 比如blogId: 14, commentId: 2

    const apiSchema = "/api/:blogId/comments/:commentId"
    const apiArgs = {
      blogId: 14,
      commentId: 2,
    }
    apiSchema.replace(/:(\w+)/g, (_, p) => {
      return apiArgs[p]
    })
    

    会返回

    '/api/14/comments/2'
    

    资料推荐

    [JavaScript 正则表达式迷你书( 1.1 版).pdf]( https://github.com/qdlaoyao/js-regex-mini-book/raw/master/JavaScript 正则表达式迷你书( 1.1 版).pdf): 一本小册子, 后面有个速查表非常方便

    https://javascript.info/regular-expressions: 非常棒的在线教程

    https://regexr.com/: 非常好用的正则测试网站

    扩展阅读

    这是一定没人会仔细看的一节, 放最后, 供感兴趣的读者参考

    正则表达式的定义

    摘自: Introduction to the Theory of Computation

    豆瓣链接: 计算理论导引(英文版·第 3 版)

    正则表达式 R 是一种language, P64

    image-20220628210619053

    以上表述中的一些符号含义, P44

    image-20220628212703461

    JS 正则表达按照定义来看, 1-6 条可以分别对应

    1. /a/对应"a"
    2. /./对应任意字符
    3. //对应空
    4. /a|b/对应"a"或"b"
    5. /ab/对应"ab"
    6. /a*/对应任意数量的"a"

    注意: 这不是一个自循环的定义, P65

    image-20220628210822554

    什么是language, P14

    image-20220628211555735

    什么是string, P14

    image-20220628211809153

    什么是alphabet, P13

    image-20220628212057818

    正则表达式的局限性

    image-20220628214147331

    正则表达式只能处理有限的 context-free language

    像嵌套匹配的括号就没法用一个正则去描述

    是否是 context-free language 可以用Pumping lemma去判断


    Creative Commons License

    MXXXXXS

    This work is licensed under a Creative Commons Attribution 4.0 International License

    17 条回复    2022-09-29 11:17:49 +08:00
    tanranran
        1
    tanranran  
       2022-06-28 23:33:25 +08:00
    很棒!
    Jat001
        2
    Jat001  
       2022-06-28 23:55:25 +08:00
    说单词字符不如说 a-zA-Z0-9_ 减少歧义
    Moeyua
        3
    Moeyua  
       2022-06-29 00:17:37 +08:00 via iPhone
    迷你书的链接 404 了
    geelaw
        4
    geelaw  
       2022-06-29 00:47:54 +08:00   ❤️ 2
    >多组模式之一
    >使用()来表示一组模式
    >
    >/[(a-c)(e-g)]/
    >匹配
    >
    >"a", "b", "c"
    >"e", "f", "g"

    这似乎是误解,在 [] 里面 () 没有特别的含义,/[(a-c)(e-g)]/ 恰好匹配 abcefg() 这 8 个字符里的任意一个,等同于 /[abcefg)(]/。

    >扩展阅读
    >正则表达式的定义

    这里你应该让读者注意实用中的正则表达式和计算理论意义下的正则表达式是不同的,实用意义的正则表达式(例如 JavaScript )的可以描述 non-regular language ,例如 /[01]*\1/ 所匹配的串的集合就不是 regular ,也不是 context-free 。再比如,零宽断言(包括 \b 等)也不能直接用计算理论意义下的正则表达式表达。

    >正则表达式 R 是一种 language, P64

    这是误解,正则表达式描述了一个 regular language ,但本身不是一个 language ,给定字母表的所有正则表达式的集合是另一个 language ,且后面这个 language 不是 regular 。

    >JS 正则表达按照定义来看, 1-6 条可以分别对应
    >2. /./对应任意字符
    >3. //对应空

    这是误解,2 (\varepsilon) 对应的是 //,它匹配长度为 0 的字符串,3 (\varnothing) 对应的是 /[]/,它不匹配任何字符串。

    >正则表达式的局限性
    >正则表达式只能处理有限的 context-free language
    >像嵌套匹配的括号就没法用一个正则去描述
    >是否是 context-free language 可以用 Pumping lemma 去判断

    什么是“有限的 context-free language”?有限 language 都是 regular 。此外,pumping lemma 不能用来证明一个语言是 regular (可以用来证明一个语言不是 regular )。
    Trello
        5
    Trello  
       2022-06-29 00:58:17 +08:00 via iPhone
    一直都感觉正则好难学。。
    MXXXXXS
        6
    MXXXXXS  
    OP
       2022-06-29 08:42:01 +08:00
    @geelaw 感谢! 果然需要发出来才能暴露自己认知的问题
    SuperMaxine
        7
    SuperMaxine  
       2022-06-29 09:07:56 +08:00 via Android
    请教下楼主,没太懂倒数第二句话
    > 像嵌套匹配的括号就没办法用一个正则去描述
    啥叫嵌套匹配的括号呀😂
    yaocai321
        8
    yaocai321  
       2022-06-29 09:57:47 +08:00
    下次抄你的
    MXXXXXS
        9
    MXXXXXS  
    OP
       2022-06-29 10:31:24 +08:00
    hunter0122
        10
    hunter0122  
       2022-06-29 12:42:41 +08:00
    下次我也要抄你的 xd
    MXXXXXS
        11
    MXXXXXS  
    OP
       2022-06-29 13:14:14 +08:00
    SuperMaxine
        12
    SuperMaxine  
       2022-06-29 15:01:54 +08:00   ❤️ 1
    @MXXXXXS 感谢给出链接,但是看过之后我觉得您好像有些概念混淆了。链接中证明了 balance brackets 语言不是正规语言,可是正则表达式并不是一个 balance brackets 语言。如果您点进对 balance brackets 语言的超链接,可以看到其定义是:字母表={`(`,`)`},且`(`和`)`的数量相等。但正则表达式中,括号是操作符而不是字符(虽然字母表中可以有`(`和`)`,通过转义`\(`和`\)`来表达,但是作为字符时并不要求两者数量相等),所以这个例子并不能证明正则表达式无法表达全部的上下文无关语言。

    实际上,我们实际使用的正则表达式是可以表达所有上下文无关语言的:见 https://www.npopov.com/2012/06/15/The-true-power-of-regular-expressions.html#matching-context-free-languages

    我查了一会儿资料,发现这一切的混乱来自于工程上的“正则表达式”和 CS 的“正则表达式”之间的冲突。就计算机科学领域而言,您最后一个小节引用的内容是正确的:正则语言确实是上下文无关语言的子集;但现实工程中的正则表达式已经加入众多,像您前面所介绍的,可以增强表达能力的“特性”(如 lookaround 、反向引用等),从而不在是计算机科学意义上的“正则语言”,甚至不再是上下文无关语言。两者的英文表达都可以是“regular expression”,就我找到的英文文献[1][2],现在学术中好像倾向于保留 CS 中的用法而将工程上的“正则表达式”写为“regex”或“regexp”;而在中文中我觉得可以在讨论计科的内容时避免使用“正则文法”、“正则表达式”等表述而改为“正规文法”、“正规式”来避免混乱。

    前面您写的是真不错,后面因为我自己也不太懂较了会儿真,没想到看个帖子还能复习一遍编译原理哈哈哈

    [1] https://www.usenix.org/conference/usenixsecurity22/presentation/mclaughlin
    [2] https://www.researchgate.net/publication/228847164_Converting_regexes_to_Parsing_Expression_Grammars
    MXXXXXS
        13
    MXXXXXS  
    OP
       2022-06-29 15:31:26 +08:00
    @SuperMaxine 哇哦,非常感谢!这部分理论知识自己确实不熟悉,感谢您详细的解释!
    littlewing
        14
    littlewing  
       2022-06-29 19:28:21 +08:00
    我以为你要介绍正则表达式的匹配算法,用法这种东西有啥好介绍的
    sxiaojian
        15
    sxiaojian  
       2022-06-30 16:54:16 +08:00
    用法挺需求介绍的。
    感觉正则很怪,用一次需要再去了解一次
    rhli1995
        16
    rhli1995  
       2022-07-01 13:21:35 +08:00
    下次我也抄你哈哈
    rockjike
        17
    rockjike  
       2022-09-29 11:17:49 +08:00
    老哥, 准备抄你哈哈
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3176 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 00:37 · PVG 08:37 · LAX 17:37 · JFK 20:37
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.