正则表达式 regular expression(经典讲解)
说明:所谓经典讲解,是作者个人直观感觉。个人以前看过大量“正则表达式”的博客文章,但是始终未能深刻理解正则表达式。但是最近一篇有关linux的书籍(开源旅行手册)提到正则表达式,让我醍醐灌顶。
说明:本文内容主要来自《OS WORLD TRIP》又名《开源世界旅行手册》,由Kardinal著。该书的源地址:下载地址 。正则表达式的内容在部分2:地理的第26章
讲解
正则表达式
简介
对于文本内容的处理,通常使用交互方式,手工调整;但如果你对源文本比较了解,则可以采用自动化的批量处理方式,这种方式效率高、迅速快批量处理,要求根据一定规则,匹配源文本中的字符,转换为目标文本,这就要用到正则表达式。
最简单的例子,使用 regular 进行匹配,结果如下:
1 | `regular` expression |
正则表达式有许多变种:glob 表达式、基本正则表达式、perl 正则表达式、emacs 正则表达式……
运算优先级
正则表达式与数学表达式的不同在于,数学表达式执行数学运算,而正则表达式执行字符运算;相同的是,它们都按一定的优先级进行运算
运算符 | 操作 |
---|---|
\ | 转义符 |
() | 捕获、匹配、断言 |
[] | 字符类 |
*+? | 限定符 |
{} | 范围 |
^$ | 位置和顺序 |
| | 或 |
转义符
如果源文本中出现了正则表达式中的运算符,如 ( ,使用 (
无法匹配下列文本中的括弧,这时要使用 \
进行转义。用 \(
匹配。在 Emacs 和 Vim 正则表达式中正好反过来,使用 \(
表示分组,用 (
匹配字符。
1 | `(`regular expression) |
在文本中匹配“运算优先级”一节中的所有运算符,都要用这种形式:
1 | \运算符 |
在文本中匹配 \
本身,要用 \\
。
非运算符前使用 \
,则有特殊的意义,例如 \n
匹配一个换行符。常用转义字符:
常规匹配
转义字符 | 涵义 |
---|---|
. | 匹配除换行符以外的任意字符 |
\w | 匹配字母或数字或下划线或汉字 |
\s | 匹配任意的空白符 |
\d | 匹配数字 |
\b | 匹配单词的开始或结束,在字符类里代表退格 |
^ | 匹配字符串的开始,在字符类里表 示”非“ |
$ | 匹配字符串的结束 |
[aeiou] | 字符集合,匹配所包含的任意一个字符 |
反向匹配
转义字符 | 涵义 |
---|---|
\W | 匹配任意不是字母,数字,下划线,汉字的字符 |
\S | 匹配任意不是空白符的字符 |
\D | 匹配任意非数字的字符 |
\B | 匹配不是单词开头或结束的位置 |
[^aeiou] | 匹配除了aeiou 这几个字母以外的任意字符 |
特殊字符
转义字符 | 涵义 |
---|---|
\a | 报警字符(打印它的效果是电脑嘀一 声) |
\t | 制表符,Tab |
\r | 回车 |
\v | 垂直制表符 |
\f | 换页符 |
\n | 换行符 |
\e | Escape |
\0nn | ASCII 代码中八进制代码为 nn 的字符 |
\xnn | ASCII 代码中十六进制代码为 nn 的字符 |
\unnnn | Unicode 代码中十六进制代码为 nnnn 的字符 |
\cN | ASCII 控制字符。比如 \cC 代表 Ctrl+C |
\A | 字符串开头(类似^,但不受处理多行 选项的影响) |
\Z | 字符串结尾或行尾(不受处理多行选 项的影响) |
\z | 字符串结尾(类似$,但不受处理多行 选项的影响) |
\G | 当前搜索的开头 |
字符类
要想匹配数字、字母、空白很容易,因为已经有了对应这些字符集合的转义符,但是如果你 想匹配没有预定义的字符集合(比如元音字母 a、e、i、o、u),应该怎么办?
正则表达式中允许你自定义字符类,在方括号里列出它们就可以了
1 | [aeiou] |
预定义的字符集合,也可以用字符类表示,如 \d
等价于 [0-9] 有些运算符,在字符类中使用会有另一种意义,例如 ^ 表示“字符串开始”,但在字符类中却
表示 “非”,以 expression
为例,使用 [exp] 匹配:
1 | `exp`r`e`ssion |
使用 [^exp] 匹配(字符串中非 e、x、p 的字符):
1 | exp`r`e`ssion` |
而使用 ^[exp] 匹配(以 e、x 或 p 起始的字符串):
1 | `e`xpression |
限定符
在上一小节中的表格中,我们知道.
可以匹配除换行符以外的任意字符,使用.
匹配下列 文本:
1 | expression |
但是 .
每次只匹配一个字符,如果想一次匹配多个,则要使用限定符。
贪婪限定符 | 惰性限定符 | 作用 |
---|---|---|
* | *? | 匹配零次或多次 |
+ | +? | 匹配一次或多次 |
? | ?? | 匹配零次或一次 |
{3} | {3}? | 匹配三次 |
{3,5} | {3,5}? | 匹配三到五次 |
{3,} | {3,}? | 匹配三次或以上 |
下面通过实例了解限定符的区别。 es 的匹配结果
1 | expr`es`sion |
es+ 的匹配结果(e,一个或多个 s)
1 | expr`ess`ion |
es* 的匹配结果(e,零或多个 s)
1 | `e`xpr`ess`ion |
es? 的匹配结果(e,零或一个 s)
1 | `e`xpr`es`sion |
贪婪与懒惰
使用限定符进行匹配时,默认匹配尽可能多的字符。无论用 .*
还是 .+
匹配下列文本,都会匹配全部
1 | `expression` |
这种方式称为“贪婪模式”。在限定符之后加 ?
则匹配尽可能少的字符,称为“懒惰模式”。 (懒惰模式的详解:.+
匹配一个或多个任意字符,在贪婪模式中,它匹配尽可能多的字符;而懒惰模式中(.+?
),则只匹配一个字符; .{3,5}
在贪婪模式中尽可能匹配5个字符,在懒惰模 式中(.{3,5}?
)只匹配3个字符; ?
和 *
这样可以匹配零次的限定符,在懒惰模式下不匹配任何字符( .*?
、.??
))。
例如,使用贪婪模式 a.+b 匹配:
1 | `aaabab` |
使用懒惰模式 a.+?b 匹配:
1 | `aaab`ab |
分支条件
|
表示“或”,使用它进行分支选择:
例如 [a-z]+|\d+
匹配单词或数字:
1 | expression 123 |
分组、捕获、不捕获
分组
使用(表达式)
对表达式进行分组,即用小括号来指定子表达式叫做分组。例如使用 (\d{3}\.){2}
匹配下面例子中的数字:
1 | abc`123.456.`def |
\d{3}
表示三个数字, (\d{3}\.)
表示三个数字加“ . ”
为一组,{2}
表示这一组内容重复两次
捕获
在对表达式进行分组的时候,会捕获文本到自动命名的组里。可以使用 \1 \2 ......
来反向引用组。例如用 ([a-z]*)\s(\d*)
匹配下列文本, ([a-z]*)
为 \1
组, (\d*)
为 \2
组
1 | `kardinal 1234567` |
使用 \2\s\1
替换 ([a-z]*)\s(\d*)
,可以改变两个字符串的顺序
1 | 1234567 kardinal |
使用([a-z]*)\s(\d*)\1
匹配下列文本,([a-z]*)
为 \1
组, (\d*)
为 \2
组,([a-z]*)\s(\d*)\1
中\1
是反向引用组:
1 | `kardinal 1234567kardinal` |
如果分组较多,计数可能会不太方便,可以给分组指定名称,例如:
1 | (?<name>[a-z]*)\s(?<num>\d*) name、num 是分组的名称 |
使用 (?:表达式)
,则只是分组,而不捕获,下面例子中, (\d*)
为 \1
组
1 | (?:[a-z]*)\s(\d*) |
不捕获的典型应用:
用使用在或的应用中,使用或需要使用括号即捕获组,但是又不想存在捕获组,则可以使用不捕获。
1 | l(?:i|o|e)ve |
注明:反向引用提供了标识字符串中的重复字符或子字符串的方便途径。 例如,如果输入字符串包含某任意子字符串的多个匹配项,可以使用捕获组匹配第一个出现的子字符串,然后使用反向引用匹配后面出现的子字符串。捕获文本放入对应的分组是需要消耗内存的。如何后续不反向引用分组,可以采用捕获但是不分组。
零宽断言
目前为止,我们学到的正则表达式匹配,都是有“宽度”的,使用 \w+
。 匹配下面文本,会将 。
一同匹配:
1 | regular。 |
如果不想匹配符号,只匹配一个位置,就要用到“零宽断言”(匹配宽度为零,满足一定的条件的断言),零宽断言使用 (?=表达式) 的语法,例如 \w+(?=。)
,其中 (?=。)
表示。
前面的位置(先行断言)。
1 | `regular`。 |
高级应用(关于为什么叫零宽断言):
表达式写法:\w+(?=。)。\w+
| \w+(?=。)\w+
1 | `regular。zero` |
如果需要匹配后面的位置,如:
1 | 。`regular` |
则要用到后发断言 (?<=。)
,使用 (?<=。)\w+
得到上面的匹配结果
使用 (?<=<b>).*(?=</b>)
匹配标签中的内容
1 | <b>`粗体`</b> |
负向零宽断言
负向零宽断言 (?!表达式)
也是匹配一个零宽度的位置,不过这个位置的“断言”取表达式的反值,例如 (?!表达式)
表示 表达式
前面的位置,如果 表达式
不成立,匹配这个位置;如果 表达式
成立,则不匹配:
1 | `expression` |
以上为使用 .+n(?!。)
的匹配结果。注意与 .+n[^。]
匹配的区别
1 | expression |
同样,负向零宽断言也有“先行”和“后发”两种,负向零宽后发断言为 (?<!表达式)
使用 (?<![</])para(?!>)
匹配下面文本:
1 | <para>`para`表示一个段落</para> |
(?<![</])
表示 para 左边不能为 <
或 /
; (?!>)
表示 para 右边不能为 >
。
常用分组语法
分类 | 代码/语法 | 说明 |
---|---|---|
捕获 | (exp) | 匹配exp,并捕获文本到自动命名的组里 |
(?exp) | 匹配exp,并捕获文本到名称为name的组里,也可以写成(?’name’exp) | |
(?:exp) | 匹配exp,不捕获匹配的文本,也不给此分组分配组号 | |
零宽断言 | (?=exp) | 匹配exp前面的位置 |
(?<=exp) | 匹配exp后面的位置 | |
(?!exp) | 匹配后面跟的不是exp的位置 | |
(?<!exp) | 匹配前面不是exp的位置 | |
注释 | (?#comment) | 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读 |
正则表达式特殊用法
\p{script=Han} 识别汉字
\p{javaWhitespace} 匹配空字符
推荐其他网址
正则表达式全集
正则表达式简明参考 (重点在分组和反向引用)
正则表达式30分钟教程 (常用分组语法的总结)
正则表达式的先行断言(lookahead)和后行断言(lookbehind)