正则表达式是对文本模式的描述
在自然语言中,1, 2, 3
被称为 数字
, 水, 火, 风
被称为 物质
, 数字
, 物质
是对事物的描述
类似的,在一段文本中,a, b, c
可以称为 单词字符
, 文本 1, 2, 3
可以称为 数字字符
将 单词字符
, 数字字符
这些概念用符号表示,这些符号就是正则表达式
正则表达式是一种语言, 专用于模式匹配领域的语言(DSL, Domain-specific language)
正则表达式是为了解决模式匹配这个问题产生的一种方法
其有很多种实现, 不同编程语言有不同的正则表达式实现,表达式的语法也有差异
这里讨论的是 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|b|c/
/[a-c]|[e-g]/
()
可以用来对匹配到的内容分组
比如版本号匹配, 使用
/(\d+).(\d+).(\d+)/
来匹配
"2.3.4"
使用match
"2.3.4".match(/(\d+).(\d+).(\d+)/)
返回
['2.3.4', '2', '3', '4']
返回的第一项是整个匹配结果,其余内容是各个括号捕获到的内容
?
代表某个模式的 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 | 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
上面的表达式出现了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://javascript.info/regular-expressions: 非常棒的在线教程
https://regexr.com/: 非常好用的正则测试网站
这是一定没人会仔细看的一节, 放最后, 供感兴趣的读者参考
注意:实用中的正则表达式和计算理论意义下的正则表达式是不同的
这里介绍一下计算理论中的正则表达式
本节内容修正感谢:geelaw
摘自: Introduction to the Theory of Computation
豆瓣链接: 计算理论导引(英文版·第3版)
正则表达式描述了一个 regular language, P64
以上表述中的一些符号含义, P44
JS正则表达按照定义来看, 1-6条可以分别对应
- /a/对应"a"
- //对应空字符串
- /[]/对应不匹配任何字符串
- /a|b/对应"a"或"b"
- /ab/对应"ab"
- /a*/对应任意数量的"a"
注意: 这不是一个自循环的定义, P65
什么是language, P14
什么是string, P14
什么是alphabet, P13
正则表达式只能处理一部分的context-free language
像嵌套匹配的括号(balanced brackets)就没法用一个正则去描述
可以用Pumping lemma去证明不是context-free language(不能证明是,但可以证明不是)
This work is licensed under a Creative Commons Attribution 4.0 International License