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

2022-06-28 23:28:17 +08:00
 MXXXXXS

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

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

原地址: 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")

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

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

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 里金额字段的识别

以及相关测试用例

内容提取

其中/\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

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

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

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

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

什么是language, P14

什么是string, P14

什么是alphabet, P13

正则表达式的局限性

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

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

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


MXXXXXS

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

3145 次点击
所在节点    分享创造
17 条回复
tanranran
2022-06-28 23:33:25 +08:00
很棒!
Jat001
2022-06-28 23:55:25 +08:00
说单词字符不如说 a-zA-Z0-9_ 减少歧义
Moeyua
2022-06-29 00:17:37 +08:00
迷你书的链接 404 了
geelaw
2022-06-29 00:47:54 +08:00
>多组模式之一
>使用()来表示一组模式
>
>/[(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
2022-06-29 00:58:17 +08:00
一直都感觉正则好难学。。
MXXXXXS
2022-06-29 08:42:01 +08:00
@geelaw 感谢! 果然需要发出来才能暴露自己认知的问题
SuperMaxine
2022-06-29 09:07:56 +08:00
请教下楼主,没太懂倒数第二句话
> 像嵌套匹配的括号就没办法用一个正则去描述
啥叫嵌套匹配的括号呀😂
yaocai321
2022-06-29 09:57:47 +08:00
下次抄你的
MXXXXXS
2022-06-29 10:31:24 +08:00
hunter0122
2022-06-29 12:42:41 +08:00
下次我也要抄你的 xd
MXXXXXS
2022-06-29 13:14:14 +08:00
SuperMaxine
2022-06-29 15:01:54 +08:00
@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
2022-06-29 15:31:26 +08:00
@SuperMaxine 哇哦,非常感谢!这部分理论知识自己确实不熟悉,感谢您详细的解释!
littlewing
2022-06-29 19:28:21 +08:00
我以为你要介绍正则表达式的匹配算法,用法这种东西有啥好介绍的
sxiaojian
2022-06-30 16:54:16 +08:00
用法挺需求介绍的。
感觉正则很怪,用一次需要再去了解一次
rhli1995
2022-07-01 13:21:35 +08:00
下次我也抄你哈哈
rockjike
2022-09-29 11:17:49 +08:00
老哥, 准备抄你哈哈

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

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

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

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

© 2021 V2EX