正则表达式在我们平时的工作中相当的常用,无论是写jmeter脚本,或者写自动化脚本,都可能会用到正则表达式。这篇文章主要梳理正则表达式的常用知识点,并结合一些具体的例子,让大家能掌握使用正则表达式的正确姿势,以至于有打通“任督二脉”之效。
理论知识
概念
- 正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符串、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。
- 正则表达式的英文是:Regular Expression,即“描述某种规则的表达式”之意。所以在很多编程语言中,都以regex、regexp等命名正则表达式模块,比如在Python中定义了re模块用于处理正则表达式。
正则表达式发展史
- 正则表达式最早追溯至1940年代科学家对人类神经系统的工作原理的研究,使用数学方法(数学符号)来描述神经网络。当时,美国新泽西州的Warren McCulloch和出生在美国底特律的Walter Pitts这两位神经生理方面的科学家,研究出了一种用数学方式来描述神经网络的新方法,他们创新地将神经系统中的神经元描述成了小而简单的自动控制元,从而作出了一项伟大的工作革新。大家可以想象下,为啥正则表达式会起源于神经网络?
- 随后,大名鼎鼎的Unix之父——Ken Thompson于1968年发表了文章《正则表达式搜索算法(Regular Expression Search Algorithm)》,并且将正则表达式这一符号系统引入了他自己开发的编辑器qed以及之后的编辑器ed中,然后又被移植到了大名鼎鼎的文本搜索工具grep中。自此,正则表达式被广泛应用到各种Unix系统或类Unix系统(如Mac系统、Linux系统)的工具中。
- 由于正则表达式异常强大而实用的功能,越来越多的语言和工具引入了正则表达式。不过遗憾的是,始终没有确立正则表达式方面的标准,导致各语言与工具中的正则表达式虽然功能上大体类似,但细微差别仍然不少。于是,诞生于1986年的POSIX开始进行标准化的尝试。
- 1988年6月,Larry Wall开发的Perl语言发布第2版,其中所引入的正则表达式引擎大放异彩。Perl 2的正则表达式引擎源于Henry Spencer编写的regex的增强版。
- 之后,正则表达式在各种计算机语言或各种应用领域进一步得到了更为广泛而普遍的应用和发展。
功能
正则表达式的功能概括讲主要有四个:
- 匹配给定的文本。
- 通过正则表达式,可以提取我们期望得到的文本。
- 替换指定的文本。
- 根据给定的文本,将字符串切分成多个子串。
特点
正则表达式的特点,概括起来是:
- 灵活性、逻辑性和功能性非常强。
- 可以用非常精简的语句,来操作字符串。
因为语句的精简,所以对于很多新手来说,正则表达式会显得晦涩难懂。但是一旦你掌握它,将会大大提升你的工作效率,会让很多复杂的代码逻辑,变的精简。
正则表达式引擎
何为“引擎”?我的理解就是能驱动整个系统运转,比如汽车发动机引擎,主要部件是气缸,是整个汽车的动力源泉。同样正则表达式引擎就是能驱动实现正则表达式功能(匹配、替换、提取)的程序,目前主流的
- NFA (Non-deterministic finite automaton) : 非确定型有穷自动机 ,以表达式为基础,遍历字符串查找匹配。使用NFA引擎的程序有:java、less、more、python、sed、vi、grep等。
- DFA( Deterministic finite automaton ):确定型有穷自动机,以文本为基础,遍历字符串,查找匹配表达式。使用DFA引擎的程序有:awk、egrep、mysql等。
可能大家会觉得比较难理解,毕竟比较偏理论。这里和大家解释下几个关键字:
**有穷:**表示有限的意思,表示有限次数内能得到结果。
自动机:其实起的名称而已,可以理解成引擎能根据规则自动匹配,不需要人为干预。
确定型和非确定型: 假设有一个字符串(text=abc)需要匹配,在没有编写正则表达式的前提下,就直接可以确定字符匹配顺序的就是确定型,不能确定字符匹配顺序的则为非确定型。
为了更直观的让大家理解NFA和DFA,从网上爬了两种动图(来自知乎—猪哥的Python)。(https://zhuanlan.zhihu.com/p/107836267)
DFA引擎执行原理动图如下所示:
NFA引擎执行原理动图如下所示:
正则表达式的语法
正则表达式由普通字符和元字符组成,其中普通字符主要就是我们常见的字母、数字、汉字、下划线以及没有特殊含义的标点符号等。下面我帮大家归类整理了常见的特殊字符(参考了菜鸟教程(https://www.runoob.com/regexp/regexp-tutorial.html)并增加了自己的理解)。
非打印字符
字符 | 描述 |
---|---|
\n | 匹配一个换行符 |
\t | 匹配一个制表符 |
\r | 匹配一个回车符 |
\s | 匹配任何空白字符,包括空格、制表符、换页符等等 |
\S | 匹配任何非空白字符 |
\f | 匹配一个换页符 |
\v | 匹配一个垂直制表符 |
限定符—数量
字符 | 描述 |
---|---|
* 等价于 {0,} | 匹配前面的子表达式0次或多次 |
+ 等价于 {1,} | 匹配前面的子表达式1次或多次 |
? 等价于 {0,1} | 匹配前面的子表达式0次或1次 |
{n} | n是一个非负整数。匹配前面的子表达式n次 |
{n,m} | m和n均为非负整数,并且n<=m,匹配前面的子表达式至少n次最多m次 |
{n,} | n是一个非负整数,匹配前面的子表达式至少n次 |
限定符主要用于对匹配内容数量的限制,注意我在上面表格中列出了三组等价关系的写法。另外这里还牵扯到一个知识点,贪婪匹配和非贪婪匹配:
- 星号*和加号+限定符都是贪婪的,也就是他们会尽可能多的匹配文字。只需要在后面加上一个问号?就能实现非贪婪匹配或最小匹配。
举个例子, 源字符串是aaabaaab,假如正则表达式是:.b,则最终能匹配到:aaabaaab,这就是贪婪匹配。如果正则表达式加上?:.*?b*,则最终能匹配到两个aaab,相对来讲这就是非贪婪匹配。
字符集合—数字/字母/其他字符
字符 | 描述 |
---|---|
\d 等价于 [0-9] | 匹配数字0到9之间的数字 |
\D 等价于 [ ^0-9] | 匹配非数字0到9的字符 |
\w 等价于 [A-Za-z_0-9] | 匹配包括下划线的任何单词字符 |
\W 等价于 [ ^A-Za-z_0-9] | 匹配非下划线、单词字符 |
\s 等价于[ \f\n\r\t\v] | 匹配任何的非打印字符 |
\S 等价于[ ^\f\n\r\t\v] | 匹配任何的打印字符 |
. | 匹配除了\n之外的任意单个字符 |
需要注意在上面表格中分别列出了等价写法,主要是为了方便大家的理解和记忆。
定位符—字符边界
字符 | 描述 |
---|---|
^ | 匹配输入字符串开始的位置 |
$ | 匹配输入字符串结尾的位置 |
\b | 匹配一个单词边界,即字与空格间的位置 |
\B | 非单词边界匹配 |
^和$应该比较好理解,\b和\B表示的是单词的边界,我们后面会通过具体的例子来帮助大家理解它的含义。
选择符—分组
字符 | 描述 |
---|---|
(pattern) | 匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到 (结果会被存储起来),需要注意的是,如果要匹配括号,需要使用转义字符’(’ 或 ‘)’。 |
\number | 反向引用,可以在正则表达式中利用数字下标number引用()分组的Matchers集合的内容,比如: 源字符串是abcdebbcde,正则表达式是:([ab])\1,最终会匹配到bb。 |
a|b | 匹配字符串a或者b,是“或”的关系 |
(?:pattern) | 匹配pattern,但是并不获取匹配结果(和上面的(pattern有区别))。也就是说不将匹配结果 存储起来,供后面可以获取使用。比如:源字符串是:industry|indestries,正则表达式是industr(?:y|ies),最终能匹配到industry和indestries。 |
(?=pattern) | 正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串, 但是不获取匹配结果,且不消耗字符串。比如:源字符串是:Windows2000,正则表达式是Windows(?=95|98|NT|2000),最终能匹配的是:windows。如果源字符串是:Windows3.1,则不能匹配。 |
(?!pattern) | 正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串, 但是不获取匹配结果,且不消耗字符串。这个正好和(?!pattern)是相反的。比如:源字符串是:Windows3.1,正则表达式还是:Windows(?=95|98|NT|2000),则最终能匹配到字符串:Windows。 |
(?<=pattern) | 逆向肯定预查,与正向肯定预查类似,只是方向相反。比如:源字符串是:2000Windows,正则表达式是:(?<=95|98|NT|2000)Windows,能匹配到字符串Windows。同样的如果源字符串是3.1Windows,则不能匹配。 |
(?<!pattern) | 逆向否定预查,与正向否定预查类似,只是方向相反。比如:源字符串是:3.1Windows,正则表达式是:(?<=95|98|NT|2000)Windows,同样的如果源字符串是2000Windows,就无法匹配。 |
运算符优先级—从高到低排列
正则表达式从左到右进行计算,并遵循优先级顺序,这跟大家常见的算术表达式非常类似。相同优先级的从左到右进行运算,不同优先级的运算先高后低。下表中的优先级是从高到低排列。
字符 | 描述 |
---|---|
\ | 转义字符 |
(), (?😃, (?=), [] | 圆括号和方括号 |
*, +, ?, {n}, {n,}, {n,m} | 限定符 |
^, $, \任何元字符、任何字符 | 定位点和序列(即:位置和顺序) |
| | 或操作 |
总结
文中主要梳理了正则表达式的基础知识点,个人觉得大家完全没必要记忆,只需要在使用的时候能知道用什么即可。然后再来翻阅这篇文章中帮大家总结的知识点。