正则表达式学习笔记
本文最后更新于:2024年9月19日 晚上
0x00 学习资料
0x01 什么是正则表达式
维基上解释如下:
正则表达式(英语:Regular expression,常简写为regex、regexp),又称规律表达式、正则表示式、正则表示法、规则表达式、常规表示法,是计算机科学计算机科学)概念,用简单字符串来描述、匹配文中全部匹配指定格式的字符串字符串,现在很多文本编辑器文本编辑器都支持用正则表达式搜索、取代匹配指定格式的字符串。
个人理解:
正则表示式是一种用来描述字符串匹配模式的语法,使用正则表达式可以从字符串中找到符合匹配模式的结果。这里的匹配模式是指某种自定的规则,该规则描述了你想要的结果在整个字符串中出现的规律。使用正则表达式就类似在windows平台下使用的通配符*、?,但是其功能要强大得多,同时语法也丰富得多。
0x02 基本匹配
最基本的匹配就是输入什么就匹配什么, 例如:一个正则表达式 the
,它表示一个规则:由字母t
开始,接着是h
,再接着是e
。
正则表达式是大小写敏感的,所以the
不会匹配The
。
“the” => The fat cat sat on the mat.
PS:书写正则表达式时,通常会将模式的语法写在/pattern/
两个斜杠之间,用来标识其使用正则表达式
0x03 元字符
在基本匹配中,有些字符无法通过基本匹配的方式直接匹配,这些字符是元字符。元字符不代表他们本身的字面意思,他们都有特殊的含义。以下是一些元字符的介绍:
元字符 | 描述 |
---|---|
. | 句号匹配任意单个字符除了换行符。 |
[ ] | 字符类。匹配方括号内的任意字符。 |
[^ ] | 否定字符类。匹配除了方括号里的任意字符 |
* | 匹配>=0个重复的在*号之前的字符。 |
+ | 匹配>=1个重复的+号前的字符。 |
? | 标记?之前的字符为可选. |
{n,m} | 匹配num个大括号之前的字符或字符集 (n <= num <= m). |
(xyz) | 字符集,匹配与 xyz 完全相等的字符串. |
| | 或运算符,匹配符号前或后的字符. |
\ | 转义字符,用于匹配一些保留的字符 `[ ] ( ) { } . * + ? ^ $ \ |
^ | 从开始行开始匹配. |
$ | 从末端开始匹配. |
-1- 点运算符.
点运算符表示任意单个字符,这里要注意两点,
- 点运算符代表至少有一个字符,也就是点运算符至少是占一位的
- 点运算符不匹配换换行符
\r
、\n
,要匹配全体字符,要使用(.|\r|\n)
“.ar” => The car parked in the garage.
-2- 字符类[]
方括号用来指定一个字符类,字符类表示的是该集合中的任意一个元素,无论字符类中包含了多少个字符,在匹配时也只占一位。
“[Tt]he” => The car parked in the garage.
在字符类中,大部分元字符是失效的,这些元字符在方括号内只表示它们字符本身,但是-
和^
和\
有其他含义。
-
表示一个字符范围,指定从其前一位的ASCII值到其后一位的ASCII值的中间的全部字符,常用的有[A-Z]表示全部大写字母,[a-z]表示全部小写字母,[0-9]表示全部数字。如果想要表示
-
本身,可以将其放在字符类的第一位或者最后一位,如果一定要将其放在字符类的两个字符中间,可以使用转义字符\
来表示其本身。表示转义字符
\
本身在字符类内依然要用\\
来表示。
-3- 否定字符类[^]
否定字符类就是反选字符类中的元素,也就是字符类中的元素都排除掉,和字符类一样,否定字符类也是只占一位的。
否定字符类也是代表的也是一个集合中的某一个元素,只是该集合是通过在全部字符集中排除掉一部分得到的。
其他元字符在否定字符类的含义与在元字符内是一样的,如果想表示^
字符,并且一定要将其放在字符类的第一位,可以使用转义字符来表示
“[^c]ar” => The car parked in the garage.
-4- 重复次数
表示重复次数的元字符有三个,分别是
*号
匹配在*号之前出现的字符0次及0次以上(大于等于0次),与点运算符连用
.*
可以匹配所有字符串“[a-z]“ => T*he car parked in the garage
+号
匹配在+号之前出现的字符1次及1次以上(大于等于1)
“c.+t” => The fat cat sat on the mat.
?号
匹配在?号之前出现的字符可能出现也可能不出现(0次或1次)
“[T]?he” => The car is parked in the garage.
以上三种符号均可以与字符类以及否定字符类连用
-5- 自定义重复次数{}
{}
是一个量词,常用来限定一个或一组字符可以重复出现的次数。
例如, 表达式 [0-9]{2,3}
匹配最少 2 位最多 3 位 0~9 的数字。?
号等价于{0,1}
。
可以省略第二个参数。 例如,[0-9]{2,}
匹配至少两位 0~9 的数字。*
号等价于{0,}
,+
号等价于{1,}
。
如果逗号也省略掉则表示重复固定的次数。 例如,[0-9]{3}
匹配3位数字
-6- 字符集()
字符集是一组写在 ()
中的子模式。()
中包含的内容将会被看成一个整体。例如, 表达式 (ab)*
匹配连续出现 0 或更多个 ab
。如果没有使用 ()
,那么表达式 ab*
将匹配连续出现 0 或更多个 b
。再比如之前说的 {}
是用来表示前面一个字符出现指定次数。但如果在 {}
前加上特征标群 ()
则表示整个标群内的字符重复 N 次。
-7- 或运算符|
或运算符就是表示或者的意思,表示运算符前后元素二选其一,但是二者必须选其一。或运算符|
的优先级最低
“(T|t)he|car” => The car is parked in the garage.
-8- 转义字符\
转义字符与其在C语言中的用法很像,反斜线 \
在表达式中用于转码紧跟其后的字符。对于特殊字符 { } [ ] / \ + * . $ ^ | ?
这些,如果想要表达其本身字符的含义则要在其前面加上反斜线 \
。
对于一些控制字符,如回车,空格之类的,也需要使用转义字符加字母来表示;此外,正则表达式还定义了一些字符集的简写,也需要用到转义字符
简写 | 描述 |
---|---|
\w | 匹配所有字母数字,等同于 [a-zA-Z0-9_] |
\W | 匹配所有非字母数字,即符号,等同于: [^\w] |
\d | 匹配数字: [0-9] |
\D | 匹配非数字: [^\d] |
\s | 匹配所有空格字符,等同于: [\t\n\f\r\p{Z}] |
\S | 匹配所有非空格字符: [^\s] |
\b | 匹配单词的开始或结束 |
\B | 匹配不是单词开头或结束的位置 |
\f | 匹配一个换页符 |
\n | 匹配一个换行符 |
\r | 匹配一个回车符 |
\t | 匹配一个制表符 |
\v | 匹配一个垂直制表符 |
\p | 匹配 CR/LF(等同于 \r\n ),用来匹配 DOS 行终止符 |
-9- 锚点
锚点有两个^
和$
,锚点可以规定匹配位置,^
是从头指定开头,$
指定结尾
例如,^(T|t)he
匹配以 The
或 the
开头的字符串。
“^(T|t)he” => The car is parked in the garage.
例如,(at\.)$
匹配以 at.
结尾的字符串。
“(at.)$” => The fat cat. sat. on the mat.
0x04 零宽断言
前面的大部分语法都是用来描述用来想要匹配本身是怎么样的,但是有时我们还希望描述被匹配到的字符串的位置,这时就需要用到零宽断言。零宽断言与前面的^
,$
,\b
,\B
类似,都是用于描述被匹配模式的位置信息的,可以用来控制想要的字符串前后需要有或者不能有什么。
之所以叫零宽断言,可能是因为该语法本身不参与匹配,只用于限定想要匹配的字符串的位置,所以其宽度为零。
零宽断言的支持和正则引擎有关,有些引擎不支持零宽断言。这里的几种断言的英文名其实比中文名更能够表明其含义。
(?=exp)
零宽正向先行断言(lookahead)(?!exp)
零宽负向先行断言(negative lookahead)(?<=exp)
零宽正回顾后发断言(lookbehind)(?<!exp)
零宽负回顾后发断言(negative lookbehind)
-1- 零宽正向先行断言(lookahead)
(?=exp)
,它断言自身出现的位置的后面能匹配表达式exp
例如,\b\w+(?=ing\b)
,就会匹配以ing结尾的单词前面部分,需要注意,零宽断言自己断言本身是不参与匹配的
“\b\w+(?=ing\b)” => I’m singing while you’re dancing
-2- 零宽正回顾后发断言(lookbehind)
(?<=exp)
,它断言自身出现的位置的前面能匹配表达式 exp。比如(?<=\bre)\w+\b
会匹配以 re 开头的单词的后半部分(除了 re 以外的部分),例如在查找 reading a book 时,它匹配 ading。
“(?<=\bre)\w+\b” => reading a book
-3- 零宽负向先行断言(negative lookahead)
(?!exp)
,与零宽正向先行断言相反,它断言的是此位置的后面不能匹配表达式 exp。
例如:\d{3}(?!\d)
匹配三位数字,而且这三位数字的后面不能是数字;\b((?!abc)\w)+\b
匹配不包含连续字符串 abc 的单词。
-4- 零宽负回顾后发断言(negative lookbehind)
(?<!exp)
,同理,零宽负回顾后发断言也就指断言位置的前面不能出现表达式exp
例如:(?<![a-z])\d{7}
匹配前面不是小写字母的七位数字。
0x05 修饰符(标记)
标记不写在正则表达式里,标记位于表达式之外,格式为/pattern/flags
-1- 修饰符g
global - 全局匹配,g 修饰符可以查找字符串中所有的匹配项。默认情况下(不带g),正则匹配会在找到第一个结果时停止,如果加上修饰符g,则会查找整个字符串,找到所有的匹配项。
-2- 修饰符i
ignore - 不区分大小写,i修饰符指定匹配时不区分大小写
-3- 修饰符m
multi line - 多行匹配,修改锚点元字符^
和$
的含义,使其锚定为每行的开头和结尾。
“/.at(.)?$/“ => The fat
cat sat
on the mat.
“/.at(.)?$/gm” => The fat
cat sat
on the mat.
-4- 修饰符s
Singleline - 单行匹配,修改.
元字符的含义,使其可以匹配所有的字符(包括\n
换行符),在这种模式下,一篇文章可以被看作一行字符串,换行符\n
也只是该行中的一个字符罢了。
需要注意的是,单行模式与多行匹配是可以同时使用的,它们二者并不冲突,因为它们针对的元字符都是不同的,同时使用这两个修饰符,就只是同时修改了
^
$
.
的含义。
0x06 贪婪和懒惰
通常情况下,正则匹配时会选择匹配尽可能多的字符,这中匹配策略被称为贪婪匹配。如果我们希望匹配时匹配最少的字符,就需要用?
将匹配策略换为懒惰匹配,?
添加在重复次数元字符的后面。
来看一个例子:aabbcb,使用a.*b
匹配时,会匹配所有的字符,也就是aabbcb,但是如果使用懒惰匹配a.*?b
,则会匹配aab(字符1-3)和ab(字符2-3)。
这里要注意的是,为什么懒惰匹配的结果不只是ab(字符2-3)呢,这时因为正则匹配中有一条规律的优先级比贪婪和懒惰要高:最先开始的匹配拥有最高的优先权,由于第一个a最先匹配,所以其拥有最高的优先级,因此可以不受贪婪和懒惰策略的影响。
懒惰限定符 | 说明 |
---|---|
*? | 重复任意次,但是尽可能少重复 |
+? | 重复1次或更多次,但是尽可能少重复 |
?? | 重复0次或1次,但是尽可能少重复 |
{n,m}? | 重复n到m次,但是尽可能少重复 |
{n,}? | 重复n次或更多次,但是尽可能少重复 |