正则表达式简明教程

设计并实现自己的编程语言系列番外(一)

Posted by John Mactavish on March 7, 2020

写在前面

正则表达式就属于那种你知道很重要,也确实很重要,但平时你又未必用得到,一旦遇到你就抓瞎的知识。 老实讲,这是有原因的;正则表达式的规则有碎片化的特点,如果你学的方法又不对,那就真的是学得杂、 忘得快。我也面临着这个问题,因为我正在实现一个词法分析器,最简单的方法自然是使用正则表达式去匹配。 可惜我已经把正则表达式的那些规则忘得差不多了。因此我今天试图从最简单的方向切入,深入浅出地串联一下 这个知识点。我不会简单地列出那些规则,因为死记硬背是不优雅的,系统的学习才是最好的。

另外,我也可以推荐一下其他的正则表达式教程。 我的记忆里讲的非常好的是 Dive into Python 中的某一章节。从一个示例出发,由浅入深,易于理解。 如果你需要完整的正则表达式规则, Github 上有一个仓库 叫 learn-regex, 也值得参考。

如果你需要练习或者验证一些正则表达式,直接打开你的 Python 交互式终端

import re

re.match(r’模式串’, r’待匹配字符串’) # r 代表 raw string literal,其中的反斜杠不会被看成字符串转义符

re.search(r’模式串’, r’待搜索字符串’)

或者可以选择“菜鸟工具” 的在线正则表达式, 那里还准备好了一些常用的模式串。

简明教程

匹配单个字符

首先是最简单的基本匹配:对于不满足特殊语法的符号,它匹配自己本身。如果把它们连起来,就匹配字符串本身。 当然,正则表达式是大小写敏感的。

“e” 匹配 “One egg can be eaten”

“let@ -“ 匹配 “let@ - vu”

像是 “-“ 这种符号,在后面的规则中有特殊的用法,但是单独使用时仍然匹配它本身。 “@” 和空格这种符号也可以进行基本匹配。

简单的,字母或数字单独出现时总是匹配它本身,其他的只要试一试就知道了。

不是所有的字符都可以打印到屏幕上显示,所以用 "\n" 匹配换行符,"\r" 匹配回车符。 嗯……熟悉的转义符号。

当你需要在正则表达式中表示后面的一些有特殊意义的符号时,你同样需要转义符号。 如用 “\[” 匹配 “[“。第一个反斜杠是字符串转义,第二个才是正则表达式转义, 只不过 Java, C 等语言中的字符串转义符刚好也是反斜杠;如果语言中用美元符转义, 而反斜杠无特殊意义,则只需要写出正则表达式的转义符号。

当你需要表达或运算的意思时,可以使用中括号。

“[abc]” 匹配 “let alpha = beta + c

这会匹配方括号内的任意字符,但只可以匹配一个字符

如果你需要的是排除一些字符,使用 "[^abc]" 排除 "a","b" 和 "c",换句话说, 它匹配 "a","b" 和 "c" 之外的所有字符

如果或运算的符号刚好在一个区间内,可以利用符号 "-" 简化表示形式。 比如 “[a-d]” 等效于 “[abcd]”, “[0-3]” 等效于 “[0123]”。

你想要匹配更大范围的符号? 还是求助于转义符号。

\w 匹配所有字母数字和下划线,等同于 [a-zA-Z0-9_](适合匹配标识符)

\W 匹配所有非字母数字下划线,等同于: [^\w]

\d 匹配数字: [0-9] (d 代表 digit)

\D 匹配非数字: [^\d]

\s 匹配所有空格字符,等同于: [\t\n\f\r\p{Z}] (s 代表 space)

\S 匹配所有非空格字符: [^\s]

我没有用双引号把它们括起来,否则在不支持 raw string literal 的语言中,你 还需要加上一个反斜杠以转义反斜杠,如 “\w”

嗯……大写的表示小写的取反,很有规律。

最后介绍功能强大的英文句号 "." ,它匹配除换行符外的所有字符

匹配多字符

之前已经说了,”abc” 这种正则表达式匹配字符串本身。多字符可以用圆括号加上符号 “ ” 表示或运算。
“(const var let)” 匹配三个关键字 “const”,”var” 和 “let”
“[abc]” 等效于 “(a b c)”

显然,模式中符号长度为一时与方括号表示法有等效关系。 括号是为了防止或运算把其他字符算了进来,因为它可能更长的正则表达式的一部分, 比如 “(a|bc|c)d” 匹配 “ad”,”bcd” 和 “cd”。

匹配变长多字符

"{n,m}" 匹配 num 个大括号之间的字符 (n <= num <= m)

对于常用的可变长范围,还有简化表示法。

"?" 标记前面的一个字符为可选,即出现 0 或 1 次

"+" 标记之前的字符出现大于等于 1 次

"*" 标记之前的字符出现大于等于 0 次

值得注意的是,这个模式起的是标记作用,本身不匹配字符,起匹配作用的是它前面的那一个字符。 比如 “a+” 的意思不是说先匹配一个 “a”,然后匹配大于等于 1 个的 “a”,而是作为一个整体 匹配大于等于 1 个的 “a”。

另外,“前面的一个字符”指的不只是确定的单个字符,也可以是通配符,它等效于可变数目个前面的通配符。 比如用 “[0-9]+” 去匹配字符串 “00457”,它的意思不是说先用 “[0-9]” 匹配 “0”,然后匹配可变数目个 “0”; 而是说匹配可变数目个 “[0-9]” ,进而匹配整个数字串。

锚点

"^" 写在模式串开头,表示从字符串开头开始匹配;"$" 写在模式串结尾,表示匹配的最后的一个字符在字符串结尾。 显然,它们本身也是只起标记作用的。

“0[xX][\da-fA-F]+” 可以匹配 “abc0x65f2

但是 “^0[xX][\da-fA-F]+” 尝试从头开始匹配,发现字符 “a” 不满足模式,直接以失败退出匹配

同理,”if.+{$” 表示无法匹配 “if(x==0){abcd”

一般在两种情况下使用正则表达式,一种是在字符串中搜寻需要的内容(如找出代码中所有的关键字), 另一种是判断某字符串是否满足某模式(如判断某字符串是不是关键字)。在第二种情况下,你可能需要 在首尾都加上锚点。

匹配模式

正则表达式还有一些匹配模式,比如是否区分大小写(默认区分)。它们可以在模式串中用特殊符号表示, 但是各个编程语言的正则表达式标准库一般都单独提供函数参数以指定匹配模式。

正则表达式默认采用贪婪匹配模式,在该模式下会匹配尽可能长的子串

用 “0[xX][\da-fA-F]+” 去匹配 “0x12feK”

贪婪匹配模式:0x12feK

惰性匹配模式:0x12feK

”[\da-fA-F]+” 可以匹配一个或多个十六进制数字,贪婪匹配选择匹配最多个(直到字符 “K” 不满足模式), 而惰性匹配选择匹配一个就好


最后附上GitHub:https://github.com/gonearewe

本系列对应的仓库 https://github.com/gonearewe/Aurora