我的正则表达式学习笔记
- 纯属自用, 欢迎参考, 如有错漏, 欢迎指出.
- 应该不会更新了.
- 这里仅介绍正则表达式本身的特质, 如果您需要查阅某种语言中应当使用何种语法来使用正则表达式, 请参阅该种语言的教程.
正则表达式简介
正则表达式是一种灵活强大的文本模式, 使用它可以方便的对文本进行匹配.
正则表达式的模式可以分为以下几种:
- 字面值字符: 数字, 字母, 空格等. 这些字符可以匹配他们自身.
- 特殊字符, 例如'.','*'等, 他们有特殊的功能.
- 字符类:用'[]'围起来的字符. 这些字符是一个集合, 方括号内的任何一个字符都可以被匹配上.
- 元字符: 他们被用于匹配特殊字符.
- 量词: 用于制定匹配的次数和范围.
- 边界符号: 用于指定字符串的边界.
正则表达式的语法
关于[]和普通字符
所有没有被指定为元字符的字符都是普通字符. (也即是说, 所有不是普通字符的字符都是元字符. 本篇会花很大的篇幅去陈列元字符. )
如果只是这样普通的将普通字符敲下来, 那么这些字符将会被作为字符串被匹配. 如果想要分别匹配, 请使用[].
[ABC]
: 被[]围起来的所有字符都会逐个的被匹配.[^ABC]
: 除了被[]围起来的字符都会逐个被匹配. (也包括换行符和空格. )[A-Z]
: 这是一个区间. 表示所有大写字母都会被匹配. 如果要匹配所有小写字母, 则应该使用[a-z], 匹配所有数字使用[0-9]. (这被称为范围表达式. 实际上, 你可以将该表达式中的字符替换为任意前者的unicode码小于后者的表达式, 意为在这个范围内选择. 例如,[1-5]
将选择1到5之间的任意正整数. )您可以在一个中括号表达式里包含多个范围, 例如
[0-9a-fA-H]
注意: 大多数特殊字符在中括号表达式里将失去其意义. 也即是说, 它们在中括号表达式中出现时, 并不用转移也可以匹配到自身. 不过例外是]
字符, -
字符和\
字符. 如果要匹配它们, 请使用转义字符.
非打印字符
非打印字符指的是那些有特殊功能的字符, 他们并不会在屏幕上被打印出来. 正则表达式也可以用于匹配这些字符.
-
\cx
匹配一个由x指定的控制字符. x必须为[ A-Z ]或者[ a-z ]. 否则, 则将c视为原意的c字符.(这里原意的c字符是个啥, 我还没有懂. 等我懂了之后我会修订此处. ) 例如: \cM将会匹配到controlM. -
\f
匹配一个换页符. -
\n
匹配一个换行符. -
\r
匹配一个回车符. -
\t
匹配一个制表符. -
\v
匹配一个垂直制表符. -
\s
匹配任何空白字符. 空白字符就是上面提到的五种. -
\S
匹配任何非空白字符.
特殊字符
这些字符有一些特殊含义. 如果你想字面意思的匹配他们, 请使用转移字符'\'使之转义.
这里陈列了特殊字符的简介. 部分特殊字符将在之后更深一步的介绍.
-
$
匹配输入字符串的结尾位置. -
^
匹配输入字符串的开头位置. 但是在方括号中使用时, 将表示"接受除了方括号中字符的其他字符. " -
()
捕获分组. 标记一个子表达式的开始和结束位置. -
*
匹配前面的子表达式零次或者多次. 如zo*
将匹配到z
,zo
,zoo
等等. -
+
匹配前面的子表达式一次或多次. 如zo+
将匹配到zo
,zoo
. -
?
匹配前面的子表达式一次或者零次. -
.
匹配除换行符之外的任何单字符. -
{}
标记限定符表达式. -
|
标记两项之间的一个选择. 例如:(z|f)ood
将匹配到zood
或者food
.
注意:*
,+
, ?
的匹配都是贪婪的. 即他们总是试图匹配更多的字符. 例如o*
会匹配ooooooo
中全部的o. 可以通过在这些符号之后加?
来实现非贪婪的匹配. (即, 匹配符合表达式的最小次数. )
限定符
限定符将告诉表达式"应该匹配多少次". 除了上面已经介绍过的*
, +
, ?
为限定符之外, 还有如下的三种限定符:
-
{n}
其中n属于[0-9]+
. 这个表达式意味着{n}
之前的字符需要被匹配确定的n次. 例如,o{2}
不能匹配Bob
, 但是可以匹配room
. -
{n,}
其中n属于[0-9]+
. 这个表达式意味着{n,}
之前的字符需要被匹配至少n次. 例如,bo{2,}k
可以匹配book
,boook
, 但是不能匹配bok
. -
{n,m}
, 其中n和m皆属于[0-9]+
且有. 这个表达式意味着将匹配至少n次, 至多m次.
定位符
我们有时希望找出仅存在于文本特殊位置的字符, 例如, 如果想要匹配一本小说中的所有章节数, 我们显然只希望匹配写在章节开头的那个数字, 对文本中间的数字则不予理会.
定位符可以实现我们这一需要. 定位符不可以同限定符一起使用.
-
^
匹配输入字符串开始的位置. 使用时, 将该符号加在正则表达式的开始. -
$
匹配输入字符串末尾的位置. 使用时, 将该符号加在正则表达式的末尾.
示例: 如果对于文本
11nihao22
使用正则表达式
/^[1-9][0-9]*/
则仅会匹配到11
若使用正则表达式
/[1-9][0-9]*$/
则仅会匹配到22
hint: 本例所使用的正则表达式的含义是: 一个无前导零的十进制正整数. 写出该正则表达式的相关知识在前面都有提及, 现在是学以致用的时候了, 相信你一定能看懂!
-
\b
匹配一个单词的单词边界. 单词边界, 即被包括空格在内的空白字符分割的边界. 注意该表达式位置敏感. 若置于正则表达式之前, 则匹配字符串的开始. (即该单词的前面是空白字符.) 置于之后则匹配字符串的末尾. -
\B
匹配非单词边界.
示例:如果对于文本
the art of aesthetic is too intricate for a carpenter.
如果使用正则表达式/\bcar/
可以匹配到carpenter
中的car
, 但/car\b/
则什么也匹配不到.
如果使用正则表达式/or\b
可以匹配到for
中的or
, 但/\bor/
则什么也匹配不到.
无论是使用\bthe
还是the\b
都能够匹配到the art
中的the
(因为显然该the既位于字符串开头也位于字符串末尾), 但是这两者都无法匹配aesthetic
中的the
.
捕获元与非捕获元
()
意为捕获分组. 感觉不太好解释, 但总之括号里的东西是一组了. 下述提到的语法都需要有显式的括号.
如果想要匹配某个词语, 可以参照如下的语法:
-
(pattern)
匹配pattern并储存这一匹配(可以留待以后使用. ) -
(?:pattern)
匹配pattern并不储存. 如果该匹配只在当下使用, 可以使用该语法避免占用过多的储存空间.
如果需要匹配某个字符之前或之后的文本而不匹配该字符自身(譬如, 获取小说的章节名, 但不希望匹配的结果里有第一章这样的字眼) 则可以使用如下的语法
-
(?<=pattern)
正向后行断言: 匹配该字符串之后的字符. 例如表达式(?<=a)bcd
将匹配a之后的bcd
-
(?<!pattern)
负向后行断言: 相当于前者的取反. 即"在任何不匹配pattern的地方匹配表达式". 例如表达式(?<!a)bcd
将匹配`除了a之后的bcd外, 所有的bcd. \
-
(?=pattern)
正向先行断言: 匹配该字符串之前的字符. 例如表达式re(?=gular)
将会匹配到regular的re
. -
(?!pattern)
负向先行断言: 相当于前者的取反.
请注意: 使用后行断言的时候要将括号捕获分组置于要匹配的字符串之后, 使用先行断言的时候要将捕获分组置于要匹配的字符串之前. (这也符合使用的直觉).
正则表达式修饰符
正则表达式修饰符用于指定匹配策略. 正则表达式修饰符并不写在正则表达式里, 而是写在正则表达式外. 它的语法规则如下:
/pattern/flags
正则表达式常用的修饰符如下:
- i: ignore 忽略大小写的匹配.
- g: global 全局匹配.
- m: multi line 多行匹配. 这种匹配方式将定位符
^
和$
的语义从定位字符串的开头和结尾修改为定位每行的开头和结尾. - s: 使
.
可以匹配任意字符. (默认情况下,.
可以匹配除换行符外的任意字符.)
正则表达式元字符
本篇前面的其实已经将元字符介绍的差不多了. 这里将陈列上面未提到较为常用的元字符.
字符 | 效果 |
---|---|
\d |
匹配一个数字字符. 等价于[0-9] |
\D |
匹配一个非数字字符. 等价于[^0-9] |
\w |
匹配数字, 字母, 下划线. |
\W |
匹配非数字, 字母, 下划线. |
\xn |
n是一个十六进制的数字, 该式将会匹配ascii码为n的字符. 注意n必须为严格的两位数. |
\un |
其中n是四位的十六进制数, 代表一个Unicode码. |
\num |
对所获取的匹配进行引用. |
正则表达式的运算优先级
优先级较高的符号优先运算, 优先级相同则从左向右运算. 与算术表达式类似.
以下为优先级从高到低排列:
运算符 | 描述 |
---|---|
\ |
转义符 |
(),(?:),(?=),(?<=),[] |
圆括号方括号 |
*,+,?,{n},{n,},{n,m} |
限定符 |
其余字符 | 元字符与普通字符 |
| |
选择运算, 相当于或. |
需要注意的是|
运算的优先级比字符结合还低, 所以m|food
的语义是匹配m或food
, 如果需要匹配mood或food
, 请使用(m|f)ood
.
随堂小测
恭喜你! 看到这里你已经学会了正则表达式的(几乎所有)内容. 来做个小测验验证一下自己的学习成果吧.
我们在日常使用的时候经常需要判断一个邮箱地址是否为有效的邮箱地址. 请写一个正则表达式, 匹配常见的邮箱地址格式.
所谓常见的邮箱地址格式遵照以下规则:
- 邮箱地址总体格式为: "用户名"+"@"+"域名"+"顶级域名"
- 用户名的规则: 包含数字, 大小写字母, 以及' . ' ,' - '和' _ '. 其中三种特殊字符既不能出现在用户名的开头部分, 也不能出现在用户名的结尾部分, 也不能连续出现多次.
- 域名: 和顶级域名之间使用' . '字符划分. 域名中可以包含特殊字符' - ', 但特殊字符不能连续出现多次.
- 顶级域名: 为2-4位的字母, 字母全为大写或者全为小写. 允许有多个顶级域名.
请根据规则独立尝试. 答案会写在下方.
确定要看答案了吗?
好的. 让我们来思考如何把邮箱地址的文字表达转换成正则表达式.
首先, 我们不希望用户粘贴一长串文本, 但中间包含了一个邮箱的情况可以通过. 所以我们的正则表达式的首尾应该包含限定符^
和$
.
我们分别考虑邮箱格式的三个部分.
首先是用户名部分, 可以包含数字, 大小写字母以及三个特殊字符. 这使得我们写出正则表达式[\da-zA-Z]
和[.\-_]
. (大多数特殊字符将在中括号内失去其意义, 所以我们不需要转义.
字符).
但是用户名要求开头和结尾不能是特殊字符. 我们通过思考可以得出: "先匹配至少一次[\da-zA-Z]
, 然后再匹配 (一个[.\-_]
加上至少一次的[\da-zA-Z]
) 零次或者多次. " 我们将其写为正则表达式. [\da-zA-Z]+([.\-_][\da-zA-Z]+)*
.
然后是域名. 域名中需要特殊处理的部分为-
不能连续出现多次. 也就是: "每次在匹配中选择: 要么匹配一个数字或字母, 要么匹配一个不连续的-
, 重复至少一次." 其中不连续匹配-
利用负向后行断言实现. 写出正则表达式:([\da-zA-Z]|(?<!-)-)+
.
然后是顶级域名. 我们可以利用分割域名与顶级域名的.
符号, 将顶级域名表述为".
加上2-4位的字母, 且至少出现一次. " 写成正则表达式为(\.[a-z|A-Z]{2,4})+
. (注意这次元字符.
出现在了中括号外部, 需要转义. )
接下来, 我们将所有部分链接到一块.修饰符使用g 别忘了用户名和域名之间的"@". 得到正则表达式:
/^[\da-zA-Z]+([.\-_][\da-zA-Z]+)*@([\da-zA-Z]|(?<!-)-)+(\.[a-z|A-Z]{2,4})+$/g
这个正则表达式能匹配诸如
i@qwq.cafe
123@qq.com
之类的邮箱, 而以下邮箱则不会匹配:
7890123
789@----.com
123@qq.cvbnm
这是我提供的答案, 如果您有更好的想法, 欢迎写在评论区.
后记
感谢我写到这里, 也感谢你看到这里.