loong博客

正则表达式(一)

目录

  • 元字符
  • 贪婪、非贪婪与独占模式
  • 分组
  • 匹配模式
  • 断言

元字符

元字符指的是在正则表达式中具有特殊意义的专用字符。元字符是构成正则表达式的基本元件,正则表达式就是由一系列的元字符组成的。

  • 特殊单字符
标识含义
.任意字符(换行除外)
\d任意数字
\D任意非数字
\w任意字母、数字及下划线
\W任意非字母、数字及下划线
\s任意空白符
\S任意非空白符
  • 空白符
标识含义
\r回车符
\n换行符
\f换页符
\t水平制表符
\v垂直制表符
\s任意空白符

平时使用正则表达式时,大部分场景使用 \s 就可以满足需求。

在计算机还没有出现之前,有一种叫做电传打字机(Teletype Model 33)的机械打字机,每秒钟可以打10个字符。但是它有一个问题,就是打完一行换行的时候,要用去0.2秒,正好可以打两个字符。要是在这0.2秒里面,又有新的字符传过来,那么这个字符将丢失。于是,研制人员想了个办法解决这个问题,就是在每行后面加两个表示结束的字符。一个叫做”回车”,告诉打字机把打印头定位在左边界,不卷动滚筒;另一个叫做”换行”,告诉打字机把滚筒卷一格,不改变水平位置。这就是”换行”(\n)和”回车”(\r)的由来。

换页符(\f)是一种控制字符,用于在打印或显示输出时将当前页面清空,并开始新的页面。‌换页符的作用是让打印设备或终端在输出内容之前,将整个屏幕或页面清空,然后继续输出接下来的内容,给人一种页面切换的效果。‌换页符的使用效果主要取决于其使用的环境。在打印环境中,换页符会将打印机的当前页面清空,并开始新的页面输出。而在终端或控制台中,换页符会将终端屏幕清空,然后输出接下来的内容。

  • 量词

无论是”特殊单字符”,还是”空白符”,它们都只能匹配单个字符。如果需要匹配单个字符,或者某个部分”重复N次”,或者”至少出现一次”等等,就需要用到表示量词的元字符。

标识含义
*0到多次
+1到多次
?0到1次
mm次
{m,}至少m次
{m,n}m到n次
  • 范围
标识含义
|或,如 a|b 代表a 或 b
[…]多选一,匹配括号中任意单个元素
[a-z]匹配 a 到 z 之间任意单个元素
[^…]取反,不能是括号中的任意单个元素

中括号[]代表多选一,可以表示里面的任意单个字符,所以任意元音字母可以用 [aeiou] 来表示。

中括号中,我们还可以用中划线表示范围,比如 [a-z] 可以表示所有小写字母。

如果中括号第一个是脱字符(^),那么就表示非,表达的是不能是里面的任何单个元素。

比如资源可能以 http:// 开头,或者 https:// 开头,也可能以 ftp:// 开头,那么资源的协议部分,可以使用 (https?|ftp):// 来表示。

正则表达式元字符

贪婪、非贪婪与独占模式

  • 贪婪匹配

尽可能进行最大长度匹配。表示次数的量词,默认是贪婪的,默认尽可能多地去匹配。

  • 非贪婪匹配

尽可能进行最短匹配。“量词”元字符后加 ? , 是非贪婪的,尽可能最短地去匹配。

正则表达式贪婪模式与非贪婪模式

左边的示例中,不加问号时正则是贪婪匹配,匹配上了从第一个引号到最后一个引号之间的所有内容;

右边的示例中,加问号时正则是非贪婪匹配,找到了最短长度的符合要求的内容;

  • 独占模式

不管是贪婪模式,还是非贪婪模式,都需要发生回溯才能完成相应的功能。

贪婪模式下的回溯,比如 regex = “xy{1,3}z” text = “xyyz”, 在匹配时,y{1,3}会尽可能长地去匹配,当匹配完 xyy 后,由于 y 要尽可能匹配最长,即三个,但字符串中后面是个 z 就会导致匹配不上,这时候正则就会向前回溯,吐出当前字符 z,接着用正则中的 z 去匹配,匹配成功。

非贪婪模式下的回溯,比如 regex = “xy{1,3}?z” text = “xyyz”, 在匹配时, 由于 y{1,3}? 代表匹配 1 到 3 个 y,尽可能少地匹配。匹配上一个 y 之后,也就是在匹配上 text 中的 xy 后,正则会使用 z 和 text 中的 xy 后面的 y 比较,发现正则 z 和 y 不匹配,这时正则就会向前回溯,重新查看 y 匹配两个的情况,匹配上正则中的 xyy,然后再用 z 去匹配 text 中的 z,匹配成功。

但是在一些场景下,我们不需要回溯,匹配不上返回失败就好了,因此正则中还有另外一种模式,独占模式,它类似贪婪匹配,但匹配过程不会发生回溯,因此在一些场合下性能会更好。 独占模式会尽可能多地去匹配,如果匹配失败就结束,不会进行回溯,具体的方法就是在量词后面加上加号(+)。比如 regex = “xy{1,3}+yz” text = “xyyz”, y{1,3}+尽可能多地匹配了两个 y , 不回溯导致正则 z 前面的 y 匹配不上。

正则表达式贪婪、非贪婪与独占模式

案例

有一篇英文文章,里面有很多单词,单词和单词之间是用空格隔开的,在引号里面的一到多个单词表示特殊含义,即引号里面的多个单词要看成一个单词。现在你需要提取出文章中所有的单词。我们可以假设文章中除了引号没有其它的标点符号,有什么方法可以解决这个问题呢?如果用正则来解决,你能不能写出一个正则,提取出文章中所有的单词呢(不要求结果去重)?

  • 文章

    we found “the little cat” is in the hat, we like “the little cat”. 其中 the little cat 需要看成一个单词好了。

正则表达式非贪婪模式应用

分组与引用

括号在正则中的功能就是用于分组。简单来理解就是,由多个元字符组成某个部分,应该被看成一个整体的时候,可以用括号括起来表示一个整体,这是括号的一个重要功能。其实用括号括起来还有另外一个作用,那就是”复用”。

  • 分组

括号在正则中可以用于分组,被括号括起来的部分”子表达式”会被保存成一个子组。

分组与编号

那分组和编号的规则是怎样的呢?其实很简单,用一句话来说就是,第几个括号就是第几个分组。

分组编号

不保存子组

在括号里面的会保存成子组,但有些情况下,你可能只想用括号将某些部分看成一个整体,后续不用再用它,类似这种情况,我们可以在括号里面使用 ?: 不保存子组。

不保存子组

括号嵌套

在括号嵌套的情况里,要看某个括号里面的内容是第几个分组怎么办?其实方法很简单,我们只需要数左括号(开括号)是第几个,就可以确定是第几个子组。

括号嵌套

  • 引用

在知道分组编号后,大部分情况下,我们就可以使用 “反斜扛 + 编号”,即 \number 的方式来进行引用, JavaScript 中是通过$编号来替换,如$1。

标识查找时引用方式替换时引用方式
Python\number,如 \1\number,如 \1
Go官方包不支持官方包不支持
JavaScript\number,如 \1$number,如 $1
  • 分组引用在替换中使用

案例

有一篇英文文章,里面有一些单词连续出现了多次,我们认为连续出现多次的单词应该是一次,比如:The little cat cat is in the hat hat hat, we like it. 要求处理后的结果是 The little cat is in the hat, we like it.

分组引用查找替换

正则表达式匹配模式

模式修饰符是通过 (? 模式标识) 的方式来表示的。 我们只需要把模式修饰符放在对应的正则表达式前,就可以使用指定的模式了。

  • 不区分大小写模式(Case-Insensitive)

在不区分大小写模式中,由于不分大小写的英文是 Case-Insensitive,对应的修饰符是 (?i) 。

案例1

匹配两个连续出现的 cat,即便是第一个 cat 和第二个 cat 大小写不一致,也可以匹配上。

不区分大小写模式

匹配两个连续出现的 cat,即便是第一个 cat 和第二个 cat 大小写不一致,也可以匹配上。

案例2

如果想要前面匹配上的结果,和第二次重复时的大小写一致,只需要用括号把修饰符和正则 cat 部分括起来,加括号相当于作用范围的限定,让不区分大小写只作用于这个括号里的内容。

不区分大小写模式

  • 点号通配模式(Dot All)

你还记得英文的点(.)有什么用吗?它可以匹配上任何符号,但不能匹配换行。当我们需要匹配真正的”任意”符号的时候,可以使用 [\s\S] 或 [\d\D] 或 [\w\W] 等。但是这么写不够简洁自然,所以正则中提供了一种模式,让英文的点(.)可以匹配上包括换行的任何字符。这个模式就是点号通配模式,对应的修饰符是 (?s) 。

点号通配模式

  • 多行匹配模式(Multiline)

通常情况下,^匹配整个字符串的开头,$ 匹配整个字符串的结尾。多行匹配模式改变的就是 ^ 和 $ 的匹配行为,使 ^ 和 $ 能匹配上每行的开头或结尾,对应的修饰符是 (?m) 。

非多行匹配模式

多行匹配模式

  • 注释模式(Comment)

在实际工作中,正则表达式可能会很复杂,这就导致编写、阅读和维护正则都会很困难。很多语言也支持在正则表达式中添加注释,让正则更容易阅读和维护,这就是正则的注释模式。正则中注释模式是使用 (?#comment) 来表示。

注释模式

正则表达式断言

只用于匹配位置,而不是文本内容本身,这种结构就是断言。常见的断言有三种:单词边界、行的开始或结束以及环视。

  • 单词边界(Word Boundary)

单词的组成一般可以用元字符 \w+ 来表示,\w 包括了大小写字母、下划线和数字(即 [A-Za-z0-9_])。那如果我们能找出单词的边界,也就是当出现了\w 表示的范围以外的字符,比如引号、空格、标点、换行等这些符号,我们就可以在正则中使用 \b 来表示单词的边界。

  • 行的开始或结束

和单词的边界类似,在正则中还有文本每行的开始和结束,如果我们要求匹配的内容要出现在一行文本开头或结尾,就可以使用 ^ 和 $ 来进行位置界定。

  • 环视

在正则中我们有时候也需要瞻前顾后,找准定位。环视就是要求匹配部分的前面或后面要满足(或不满足)某种规则。

标识查找时引用方式替换时引用方式
(?<=X>)肯定逆序环视左边是X
(?<!X>)否定逆序环视左边不是X
(?=X>)肯定顺序环视右边是X
(?!X>)否定顺序环视右边不是X

口诀:左尖括号代表看左边,没有尖括号是看右边,感叹号是非的意思。

单词边界用环视表示

表示单词边界的 \b 如果用环视的方式来写,应该是怎么写呢?单词可以用 \w+ 来表示,单词的边界其实就是那些不能组成单词的字符,即左边和右边都不能是组成单词的字符。(?<!\w) 表示左边不能是单词组成字符,(?!\w) 右边不能是单词组成字符,即 \b\w+\b 也可以写成 (?<!\w)\w+(?!\w) , 但是不能写成 (?<=\W)\w+(?=\W) ,因为\W 不能匹配行开关和结尾

环视与子组

环视中虽然也有括号,但不会保存成子组。保存成子组的一般是匹配到的文本内容,后续用于替换等操作,而环视是表示对文本左右环境的要求,即环视只匹配位置,不匹配文本内容。

断言