正则表达式 Regex 语法 总结 [MD]


目录

  • 目录
  • 正则表达式
    • 学习资料
    • 实用工具
    • 一些案例
  • 元字符
    • 常用的元字符
    • 常用的字符集简写
  • 基础语法
    • 基本匹配
    • 点运算符 .
    • 字符集 []
    • 否定字符集 ^
    • 重复次数
      • *
      • +
      • ?
      • {}
    • 或运算符 |
    • 转义字符 \
    • 锚点 ^$
  • 修正符 标志(Flags)
    • 全局搜索 g
    • 忽略大小写 i
    • 多行模式 m
    • 单行模式 s
    • 惰性匹配 U ?
    • 扩展(注释) (?x) #comment
  • 分组 ()
    • 分组使用案例
    • 常用分组语法
    • 后向引用
      • 组号 \1
      • 命名组 (?<name>exp)
    • 零宽断言
      • 正先行断言 (?=exp)
      • 负先行断言 (?!exp)
      • 正后发断言 (?<=exp)
      • 负后发断言 (?<!exp)
    • 注释 (?#comment)

我的GitHub 我的博客 我的微信 我的邮箱
baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

目录

正则表达式

学习资料

  • GitHub 高星入门教程:learn-regex
  • 正则表达式30分钟入门教程:网站、
  • Microsoft文档:快速参考、微软的.NET正则表达式教程
  • 书籍《精通正则表达式》:图书介绍,PDF版下载
  • 其他:菜鸟教程、专业的正则表达式教学网站(英文)

实用工具

  • 强大的在线工具:regex101、regexr
  • 小巧的在线工具:regexpal、Rubular
  • 最强大的工具 RegexBuddy:官网、v4.11破解版下载
  • 国产工具 Regester(基于.Net):官网、v2.0版下载
  • 其他:正则表达式引擎/风味对比

一些案例

一些案例:

  • \bhi\b.*\bLucy\b:先是一个单词hi,然后是任意个任意字符(但不能是换行),最后是Lucy这个单词
  • 0\d{2}-\d{8}:以0开头,然后是2个数字,然后是一个连字号-,最后是8个数字(电话号码)
  • \ba\w*\b:匹配以字母a开头的单词
  • ^\d{5,12}$:匹配整个字符串必须是5到12个数字
  • C:\\\\Windows\\bqt\.com:精准匹配C:\\Windows\bqt.com
  • 0\d{2}-\d{8}|0\d{3}-\d{7}:匹配两种以连字号分隔的电话号码,一种是3位区号+8位本地号(如010-12345678),一种是4位区号+7位本地号(如0376-2233445)
  • S+:匹配不包含空白符的字符串(以空白符分割所有单词、数字、汉字等内容)
  • ]+>:匹配用尖括号<>括起来的以a开头的字符串

一个复杂的案例

\(?0\d{2}[) -]?\d{8}:匹配几种格式的电话号码

  • 首先是一个转义字符\,表示后面的(会被当做普通的左小括号来处理
  • 然后元字符?代表(能出现0次或1次
  • 然后是一个0,后面跟着2个数字\d{2}
  • 然后是一个字符集[) -],代表后面会出现)-空格中的一个,它能出现0次或1次
  • 最后\d{8}代表8个数字

然而,上面那个表达式也能匹配010)12345678(022-87654321这样的不正确的格式。要解决这个问题,就需要用到分枝条件。

\(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}:这个表达式匹配3位区号的电话号码,其中区号可以用小括号括起来,也可以不用;区号与本地号间可以用连字号或空格间隔,也可以没有间隔。

元字符

元字符(MetaCharacter)不代表他们本身的字面意思,他们都有特殊的含义。注意,一些元字符写在方括号中的时候另有一些特殊的意思。

例如元字符\b代表着单词的开头或结尾,也就是单词的分界处。

虽然通常英文的单词是由空格,标点符号或者换行来分隔的,但是\b并不匹配这些单词分隔字符中的任何一个,它只匹配一个位置(意思是说,匹配结果中不包含\b)。

\b:assert position at a word boundary: (^\w|\w$|\W\w|\w\W)
声明单词边界处的位置(不包括这些边界处的分割符),可以匹配以下位置:

  • ^\w:以\w开头的位置
  • \w$:以\w结尾的位置
  • \W\w非\w+\w的位置
  • \w\W\w+非\w的位置

常用的元字符

以下是正则表达式常用的元字符的介绍:

元字符 描述
. 匹配任意单个字符,除了换行符
[ ] 字符种类。匹配方括号内的任意字符
[^ ] 否定的字符种类。匹配除了方括号里的任意字符
* 匹配>=0个重复的在*号之前的字符(任意次)
+ 匹配>=1个重复的+号前的字符(至少一次)
? 标记?之前的字符为可选(最多一次)
{n,m} 匹配[n,m]个大括号之前的字符或字符集
(xyz) 字符集,匹配与 xyz 完全相等的字符串
| 或运算符,匹配符号前或后的字符
\ 转义字符,用于匹配一些保留的字符
\num 获取第 num 个分组匹配的文本
^ 匹配字符串的开头
$ 匹配字符串的结尾

保留字符有:[ ] ( ) { } . * + ? ^ $  |

常用的字符集简写

正则表达式提供一些常用的字符集简写。如下:

简写 描述
\w 匹配所有字母、数字、下划线,等同于 [a-zA-Z0-9_]
\W 匹配所有非字母数字、下划线,等同于: [^\w]
\b 匹配单词边界,在字符类里使用代表退格
\B 匹配非单词边界,等同于: [^\b]
\d 匹配数字,等同于: [0-9]
\D 匹配非数字,等同于: [^\d]
\s 匹配所有空格字符,等同于: [\t\n\f\r\p{Z}]
\S 匹配所有非空格字符,等同于: [^\s]
. 匹配除换行符外的所有字符,等同于:[^\n]
\n 匹配一个换行符
\f 匹配一个换页符
\r 匹配一个回车符
\t 匹配一个制表符,Tab
\v 匹配一个垂直制表符
\p 匹配 CR/LF,等同于 \r\n,用来匹配 DOS 行终止符
\e Escape
\a 报警字符,打印它的效果是电脑嘀一声
\A 字符串开头,类似^,但不受处理多行选项的影响
\z 字符串结尾,类似$,但不受处理多行选项的影响
\Z 字符串结尾或行尾,不受处理多行选项的影响

基础语法

正则表达式到底是什么东西?在处理字符串时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。

正则表达式是一组由字母、数字和符号组成的特殊文本,它可以用来从文本中找出满足你想要的格式的句子。

Regular expression这个词比较拗口,我们常使用缩写的术语regexregexp

基本匹配

正则表达式默认是大小写敏感的。

表达式 `the` 表示一个规则:由字母`t`开始,接着是`h`,再接着是`e`
"the" => The fat cat sat on the mat.

点运算符 .

.匹配任意单个字符,但不匹配换行符

表达式 `.ar` 匹配一个任意字符后面跟着是`ar`的字符串
".ar" => The car parked in the garage.

字符集 []

  • 方括号用来指定一个字符集(也叫做字符类),表达式 [ab]表示匹配一个"a"或一个"b"
  • 在方括号中使用连字符-来指定字符集的范围,例如表达式 [a-dg] 相当于[abcdg]或者a|b|c|d|g
  • 方括号表示一个字符集中的字符允许在一个字符串中的某一特定位置出现
  • 方括号中的字符集不关心顺序
  • 方括号中的保留字符不需要转义
表达式 `[Tt]he` 匹配 `the` 和 `The`
"[Tt]he" => The car parked in the garage.

表达式 `ar[.]` 仅匹配 `ar.`字符串
"ar[.]" => A garage is a good place to park a car.

否定字符集 ^

一般来说 ^ 表示一个字符串的开头,但它用在一个方括号内部的开头的时候,它表示这个字符集是否定的。

例如:[aeiou]匹配任何一个英文元音字母,[^aeiou]匹配除了aeiou这几个字母以外的任意字符

表达式 `[^c]ar` 匹配一个后面跟着`ar`的除了`c`的任意字符
"[^c]ar" => The car parked in the ggarage.

重复次数

后面跟着元字符 +*?{}的,用来指定匹配子模式的次数。

*

  • *号匹配 在*之前的字符出现>=0次,相当于{0,}
  • *字符和.字符搭配可以匹配所有的字符.*(非常重要)
  • *可以和表示匹配空格的符号\s连起来用
表达式`\s*cat\s*`匹配0或更多个空格开头和0或更多个空格结尾的cat字符串
"\s*cat\s*" => The fat cat sat on the concatenation.

+

+号匹配+号之前的字符出现 >=1 次,相当于{1,}

表达式 `c.+t` 匹配以首字母`c`开头以`t`结尾,中间跟着至少一个字符的字符串
"c.+t" => The fat cat sat on the mat.

注意:以上匹配结果只有一个,可以通过 regex debugger 查看匹配过程

?

?号匹配?号之前的字符出现 0 或 1 次,相当于{0,1}

表达式 `[T]?he` 匹配字符串 `he` 和 `The`
"[T]?he" => The car is parked in the garage.

"[T]he" => The car is parked in the garage.

{}

在正则表达式中 {} 是一个量词,常用来限定一个或一组字符可以重复出现的次数。

表达式 `[0-9]{2,3}` 匹配最少 2 位最多 3 位 0~9 的数字
"[0-9]{2,3}" => The number was 9.9997 but we rounded it off to 10.0.

表达式 `[0-9]{2,}` 匹配至少两位 0~9 的数字
"[0-9]{2,}" => The number was 9.9997 but we rounded it off to 10.0.

表达式 `[0-9]{3}` 匹配固定3位数字
"[0-9]{3}" => The number was 9.9997 but we rounded it off to 10.0.

或运算符 |

或运算符(分枝条件)表示或,用作判断条件。

表达式 `(T|t)he|car` 匹配 `(T|t)he` 或 `car`
"(T|t)he|car" => The car is parked in the garage.

使用或运算符时,要注意各个条件的顺序。

例如,表达式\d{5}-\d{4}|\d{5}用于匹配美国的邮政编码。美国邮编的规则是5位数字,或者用连字号间隔的9位数字。
如果你把它改成\d{5}|\d{5}-\d{4}的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。
原因是匹配分枝条件时,将会从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管其它的条件了。

转义字符 \

反斜线 \ 在表达式中用于转码紧跟其后的字符。用于指定 { } [ ] / \ + * . $ ^ | ? 这些特殊字符。如果想要匹配这些特殊字符则要在其前面加上反斜线 \

表达式 `\.?` 表示选择性匹配字符`.`
"(f|c|m)at\.?" => The fat cat sat on the mat.

锚点 ^$

这两个元字符常用来校验电话、IP等信息,很少用来检索或替换,因为其条件太苛刻,功能类似于 startsWith/endsWith 的增强版,只不过startsWith/endsWith 并不支持正则表达式

在正则表达式中,想要匹配指定开头或结尾的字符串就要使用到锚点。^ 指定开头,$ 指定结尾。

^ 用来检查匹配的字符串是否在所匹配字符串的开头。

例如,在 abc 中使用表达式 ^a 会得到结果 a,但如果使用 ^b 将匹配不到任何结果,因为在字符串 abc 中并不是以 b 开头。

表达式 `^(T|t)he` 匹配以 `The` 或 `the` 开头的字符串
"^(T|t)he" => The car is parked in the garage.

"(T|t)he" => The car is parked in the garage.

同理,$ 号用来匹配字符是否是最后一个。

表达式 `(at\.)$` 匹配以 `at.` 结尾的字符串
"(at\.)$" => The fat cat. sat. on the mat.

"(at\.)" => The fat cat. sat. on the mat.

修正符 标志(Flags)

标志也叫模式修正符,因为它可以用来修改表达式的搜索结果。这些标志可以任意的组合使用,它也是整个正则表达式的一部分。

除了全局搜索外(最基本的功能,必须支持),常用的修正符一般都有两种用法:

  • 将修正符单独作为一个参数,如 Java 中的 Pattern 类就需要提供了一个flags参数(默认为0),此值需要使用 Pattern 类中定义的常量,例如Pattern.CASE_INSENSITIVE

  • 将修正符集成到正则表达式中,例如,将(?i)放在正则表达式的前面,和使用 Pattern.CASE_INSENSITIVE的效果是一样的
    • (?i):声明此后的正则表达式不区分大小写
    • (?imsU):声明此后的正则表达式同时满足i、m、s、U等所有模式
    • (?x) #声明此后的内容为注释,其他一般是放在正则前面,而他要放在最后
public static Pattern compile(String regex) {
    return new Pattern(regex, 0);
}

public static Pattern compile(String regex, int flags) {
    return new Pattern(regex, flags);
}

//This private constructor is used to create all Patterns. 
//The pattern string and match flags are all that is needed to completely describe a Pattern.
private Pattern(String p, int f) {...}

全局搜索 g

修饰符 g 常用于执行一个全局搜索匹配,即不仅仅返回第一个匹配的,而是返回全部。

global. All matches (don't return after first match)

"/.(at)/g" => The fat cat sat on the mat.

"/.(at)/" => The fat cat sat on the mat.

一般情况下,IDE、正则匹配工具、编程语言默认都是全局模式,除非手动关闭。

Matcher m = Pattern.compile(".(at)").matcher("The fat cat sat on the mat.");
while (m.find()) {
    System.out.println(m.group()); //fat、cat、sat、mat
}

忽略大小写 i

修饰语 i 用于忽略大小写。

insensitive. Case insensitive match (ignores case of [a-zA-Z])

Case-insensitive matching can also be enabled via the embedded flag expression (?i)

"/The/gi" 或 "/(?i)The/g" => The fat cat sat on the mat.

"The" => The fat cat sat on the mat.

多行模式 m

多行修饰符 m 常用于执行一个多行匹配。

multi line. Causes ^ and $ to match the begin/end of each line (not only begin/end of string)

Multiline mode can also be enabled via the embedded flag expression (?m)

元字符 ^ $ 用于检查格式是否是在待检测字符串的开头或结尾,但我们如果想要它在每行的开头和结尾生效,我们需要用到多行修饰符 m

"/.at(.)?$/g" =>
The fat
cat sat
on the mat.

"/.at(.)?$/gm" 或 "/(?m).at(.)?$/g" =>
The fat
cat sat
on the mat.
//以下两个结果一致
Matcher m = Pattern.compile("(?m).at(.)?$").matcher("The fat \ncat sat \non the mat");
Matcher m = Pattern.compile(".at(.)?$",Pattern.MULTILINE).matcher("The fat \ncat sat \non the mat");

单行模式 s

如果没有使用这个标志,元字符中的.默认不能表示换行符号;使用此标志后,元字符.也可以表示换行符号,所以效果就是在使用.时将字符串视为单行

single line. Dot (.) will match any character, including newline.

Dotall mode(single-line mode) can also be enabled via the embedded flag expression (?s)

例如,对于字符串

bqt
qt

有以下几个测试 Case:

  • 正则表达式/.qt/g只可以匹配到bqt
  • 正则表达式/.qt/gs/(?s).qt/g可以匹配到bqt\nqt(\n表示一个换行符)
  • 正则表达式/.*qt/g可以匹配到bqtqt(前面没有换行符)
  • 正则表达式/.*qt/gs/(?s).*qt/g可以匹配到整个结果(默认启用贪婪匹配导致的)
  • 正则表达式/.*qt/gsU可以匹配到bqt\nqt(启用惰性匹配)

一个经常被问到的问题是:是不是只能同时使用多行模式单行模式中的一种?
答案是:不是。
这两个选项之间没有任何关系,除了它们的名字比较相似(以至于让人感到疑惑)以外。事实上,为了避免混淆,在 JavaScript、Java 中单行模式分别叫dotAllDOTALL

惰性匹配 U ?

正则表达式默认采用贪婪匹配模式,在该模式下会匹配尽可能长的子串。

有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。我们可以使用 U? 将贪婪匹配模式转化为惰性匹配模式。

例如:如果用a.*b来搜索aabab的话,它会匹配整个字符串aabab
如果用a.*?b来搜索aabab的话,它会匹配aab[1,3]ab[4,5]

为什么第一个匹配是aab[1,3]而不是ab[2,3]?简单地说,因为正则表达式有另一条比懒惰/贪婪优先级更高的规则:最先开始的匹配拥有最高的优先权——The match that begins earliest wins。

代码/语法 说明
*? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复

Ungreedy. The match becomes lazy by default. Quantifiers followed by ? will become greedy.

个人猜测(自测也OK) Ungreedy mode can also be enabled via the embedded flag expression (?U)

"/(.*at)/" => The fat cat sat on the mat.

"/(.*?at)/" => The fat cat sat on the mat.

亲测后得出的结论:

  • /(.*at)/U 能用 /(?U)(.*at)/ 替换是天经地义的(和上面几种一样)
  • 上面这两种也能用 /(.*?at)/ 替换,都具有转化为惰性匹配模式的效果
  • 但是,上面这两种和 /(.*?at)/ 不能同时使用,否则将没任何效果(负负得正)

扩展(注释) (?x) #comment

如果设定了此修正符,模式中的空白字符除了被转义的或在字符类中的以外完全被忽略。

extended. Literal whitespace characters are ignored, except in character sets.

例如:

  • 正则表达式/b qt/可以匹配字符串b qt(正常模式)
  • 正则表达式/b qt/x不能匹配字符串b qt(忽略空白字符)
  • 正则表达式/b\ qt/x可以匹配字符串b qt(转义字符)
  • 正则表达式/b\sqt/x可以匹配字符串b qt(字符类)

这个修正符真正有用的地方在于:在#以及下一个换行符之间的所有字符,包括两头,都被忽略,这使得可以在复杂的模式中加入注释

Permits允许 whitespace and comments注释 in pattern.

In this mode, whitespace is ignored, and embedded嵌入的 comments starting with # are ignored until the end of a line.

Comments mode can also be enabled via the embedded flag expression (?x).

例如:

  • 正则表达式/bqt #这是注释/gx和可以匹配bqt,不可以匹配b qt
  • 正则表达式/b qt #这是注释/gx同样也是可以匹配bqt,不可以匹配b qt
  • 正则表达式/b qt(?x) #这是注释/g可以匹配b qt,不可以匹配bqt
//在Java中,以下3个 Pattern 的效果是一样的
Pattern.compile(".at")
Pattern.compile(".at(?x) #这是注释")
Pattern.compile(".at #这是注释", Pattern.COMMENTS)

分组 ()

如果想要重复多个字符,可以用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了。

例如:

  • 表达式ab+ab{1,}匹配一个a后连续出现 1 个或更多个b
  • 表达式(ab)+(ab){1,}匹配连续出现 1 个或更多个ab
表达式 `(c|g|p)ar` 匹配 `car` 或 `gar` 或 `par`
"(c|g|p)ar" => The car is parked in the garage.

分组使用案例

使用分组的一个最简单的原因是:可以将很长的、复杂的正则表达式分割成多个子表达式,以方便理解。

例如,(\d{1,3}\.){3}\d{1,3}是一个简单的IP地址匹配表达式:\d{1,3}\.匹配1到3位的数字加上一个英文句号,(){3}表示括号中的这个整体(也就是这个分组)重复3次,最后再加上一个1到3位的数字\d{1,3}

然而,上面的表达式也将匹配256.300.888.999这种不可能存在的IP地址。如果能使用算术比较的话,或许能简单地解决这个问题,但是正则表达式中并不提供关于数学的任何功能,所以只能使用冗长的分组、选择、字符类来描述一个正确的IP地址:((A)\.){3}(A)

  • ((A)\.){3} 代表前面有 3 个 A.
  • (A) 代表后面有 1 个 A

其中,上面的A代表2[0-4]\d|25[0-5]|[01]?\d\d?(不能用后向引用):

  • 2[0-4]\d 代表可以为 200 - 249 之间的所有数字
  • 25[0-5] 代表可以为 250- 255 之间的所有数字
  • [01]?\d\d? 代表可以为 0 - 199 之间的所有数字(05、08之类的也是合法的)

常用分组语法

使用小括号的时候,还有很多特定用途的语法。下面列出了最常用的一些:

分类 代码/语法 说明
捕获 (exp) 匹配exp,并捕获文本到自动命名的组里
捕获 (?exp) 匹配exp,并捕获文本到名称为name的组里,尖括号也可以写成单引号
没啥卵用 (?:exp) 匹配exp,不捕获匹配的文本,也不给此分组分配组号
零宽断言 (?=exp) 匹配exp前面的位置
零宽断言 (?<=exp) 匹配exp后面的位置
零宽断言 (?!exp) 匹配后面跟的不是exp的位置
零宽断言 (? 匹配前面不是exp的位置
注释 (?#comment) 用于提供注释让人阅读

后向引用

使用小括号指定一个子表达式后,匹配这个子表达式的文本(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理。

后向引用用于重复搜索前面某个分组匹配的文本。

组号 \1

默认情况下,每个分组会自动拥有一个组号,组号分配的规则是:

  • 从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推
  • 分组0对应整个正则表达式
  • 可以使用(?:exp)这样的语法来剥夺一个分组对组号分配的参与权
  • 实际上组号分配过程是要从左向右扫描两遍:第一遍只给未命名组分配,第二遍只给命名组分配,因此所有命名组的组号都大于未命名的组号

例如,对于正则表达式((A)(B(C))),其中有四个组,编号1为((A)(B(C))),编号2为(A),编号3为(B(C)),编号4为(C)

案例1,正则表达式(.)\1{2,}可以用来匹配连续出现多次的字符:

  • 首先,需要先匹配任意一个字符(.),其中.表示匹配除换行符外的任意字符
  • 然后,根据组号分配规则,这个单词会被捕获到编号为1的分组中,其中\1代表分组1匹配的文本
  • 最后,需要连续出现前面分组1中捕获的内容,其中{2,}代表分组1匹配的文本又连续出现了至少2次

案例2,正则表达式\b(\w+)\b\s+\1\b可以用来匹配重复的单词,像go go, 或者kitty kitty

  • 首先,需要先匹配一个单词\b(\w+)\b,其中\w表示匹配所有字母、数字、下划线
  • 然后,根据组号分配规则,这个单词会被捕获到编号为1的分组中
  • 然后,需要出现1个或多个空白符\s+
  • 最后,需要再出现一次前面分组1中捕获的内容,其中\1代表分组1匹配的文本

Java 中可以通过调用 matcher 对象的groupCount()方法来查看表达式有多少个分组,但是代表整个表达式的 group(0) 不包括groupCount()的返回值中。

命名组 (?<name>exp)

可以使用(?exp)或者(?'name'exp)指定一个子表达式的组名,这样,子表达式exp就是一个命名组了,且其组名为name

命名后,就可以使用\k反向引用这个分组捕获的内容。

使用命名组反向引用分组的方式,可以避免通过组号引用分组时因计算分组错误导致引用出错的情况。

所以上一个例子也可以写成这样:\b(?\w+)\b\s+\k\b

零宽断言

断言用来声明一个应该为真的事实。正则表达式中只有当断言为真时才会继续进行匹配。

下面的四个用于查找在某些内容之前或之后的东西(但并不包括这些内容),也就是说它们像\b ^ $那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言

他们都都属于非捕获簇(不捕获文本 ,也不针对组合计进行计数)。

先行断言用于判断所匹配的格式是否在另一个确定的格式之前,匹配结果不包含该确定格式(仅作为约束)。

例如,我们想要获得所有跟在 $ 符号后的数字,我们可以使用正后发断言 (?<=\$)[0-9\.]*
这个表达式匹配 $ 开头,之后跟着 0,1,2,3,4,5,6,7,8,9,. 这些字符可以出现大于等于 0 次。

零宽度断言如下:

符号 描述
?= 正先行断言-存在
?! 负先行断言-排除
?<= 正后发断言-存在
? 负后发断言-排除

正先行断言 (?=exp)

正先行断言(也叫零宽度正预测先行断言),它断言自身出现的位置的后面能匹配表达式exp,返回结果只包含满足匹配条件的第一部分表达式。

例如,表达式 (T|t)he(?=\sfat)

  • 首先(T|t)he表示匹配Thethe
  • 然后我们又定义了正先行断言(?=\sfat),其中\s表示匹配所有空格字符
  • 所以表达式整体表示:匹配所有(T|t)he、且其后面紧跟着(空格)fat的内容,并返回匹配的(T|t)he
表达式 `(T|t)he(?=\sfat)` 匹配所有 `(T|t)he`,且其后跟着 `(空格)fat`
"(T|t)he(?=\sfat)" => The fat cat sat on the mat.

表达式 `(T|t)he(?=\s.at)` 匹配所有 `(T|t)he`,且其后跟着 `(空格)(任意字符)at`
"(T|t)he(?=\s.at)" => The fat cat sat on the mat.

表达式 `\b\w+(?=ing\b)` 匹配以 `ing` 结尾的单词的前面部分
"\b\w+(?=ing\b)" => I'm singing while you're dancing.

负先行断言 (?!exp)

负先行断言(也叫零宽度负预测先行断言),定义和正先行断言一样,区别就是=替换成!,它断言此位置的后面不能匹配表达式exp。

表达式 `(T|t)he(?!\sfat)` 匹配所有 `(T|t)he`,且其后不跟着 `(空格)fat`
"(T|t)he(?!\sfat)" => The fat cat sat on the mat.

正后发断言 (?<=exp)

正后发断言(也叫零宽度正回顾后发断言),它断言自身出现的位置的前面能匹配表达式exp。

表达式 `(?<=(T|t)he\s)(fat|mat)` 匹配所有 (fat|mat),且其前跟着 `The(空格)` 或 `the(空格)`
"(?<=(T|t)he\s)(fat|mat)" => The fat cat sat on the mat.

表达式 `(?<=\bre)\w+\b` 会匹配以`re`开头的单词的后半部分
"(?<=\bre)\w+\b" => reading a book

表达式 (?<=\s)\d+(?=\s)匹配以空白符间隔的数字(不包括这些空白符)。

负后发断言 (?<!exp)

负后发断言(也叫零宽度负回顾后发断言),定义和正后发断言一样,区别就是=替换成!,它断言此位置的前面不能匹配表达式exp。

表达式 `(? The cat sat on cat.

匹配不包含属性的简单 HTML 标签内里的内容(?<=<(\w+)>).*(?=<\/\1>)

  • 首先(?<=<(\w+)>)指定了<(\w+)>的前缀:被尖括号括起来的单词,比如可能是
  • 然后是任意的字符串.*
  • 最后(?=<\/\1>)指定了<\/\1>的后缀:
    • 后缀里的\/用到了字符转义,表示的就是一个/
    • \1是一个反向引用,引用的正是前面的(\w+)匹配的内容
    • 这样如果前缀实际上是的话,后缀就是
  • 整个表达式匹配的是之间的内容(不包括前缀和后缀本身)

注释 (?#comment)

小括号的另一种用途是通过语法(?#comment)来包含注释。

例如:2[0-4]\d(?#匹配200-249)|25[0-5](?#匹配250-255)|[01]?\d\d?(?#匹配0-199)

要包含注释的话,最好是启用忽略模式里的空白符选项,这样在编写表达式时能任意的添加空格、Tab、换行,而实际使用时这些都将被忽略。启用这个选项后,在#后面到这一行结束的所有文本都将被当成注释忽略掉。

例如,我们可以把前面的表达式(?<=<(\w+)>).*(?=<\/\1>)写成这样:

(?<=    # 断言要匹配的文本的前缀
<(\w+)> # 查找尖括号括起来的内容,(即HTML/XML标签),比如可能是
)       # 前缀结束
.*      # 匹配任意文本
(?=     # 断言要匹配的文本的后缀
<\/\1>  # 查找尖括号括起来的内容,如果前缀实际上是的话,后缀就是
)       # 后缀结束

2020-03-30