

## 正则表达式介绍

正则表达式（Regular Expression，简称 Regex 或 RegExp）是一种用来匹配字符串中字符组合的模式。

正则表达式是一种用于模式匹配和搜索文本的工具。

正则表达式是一个由普通字符（例如字母 a 到 z）和特殊字符（称为元字符）组成的字符串，这个字符串构成了一个 **搜索模式（pattern）**

正则表达式提供了一种灵活且强大的方式来查找、替换、验证和提取文本数据。

- **查找与匹配：**在文本中找到特定模式的内容

- **替换：**将符合某种模式的文本替换为其他内容

- **验证：**检查输入的数据是否符合预期格式

- **提取和分割：**从复杂文本中提取需要的信息


位置：

```text
字符串: "abc"
位置:  0 1 2 3
       ↓ ↓ ↓ ↓
       |a|b|c|
        ↑ ↑ ↑ ↑
      位 位 位 位
      置 置 置 置
      0 1 2 3
      像 ^、$、\b、(?=...) 这些，都是匹配这些位置，而不是匹配具体的字符。
```

例子：

`^[a-zA-Z0-9_-]{3,15}$`

- `^` 表示匹配字符串的开头。
- `[a-zA-Z0-9_-]` 表示字符集，包含小写字母、大写字母、数字、下划线和连接字符 **-**。
- `{3,15}` 表示前面的字符集最少出现 3 次，最多出现 15 次，从而限制了用户名的长度在 3 到 15 个字符之间。
- `$` 表示匹配字符串的结尾。

### 字符匹配

- 普通字符：普通字符按照字面意义进行匹配，例如匹配字母 "a" 将匹配到文本中的 "a" 字符。普通字符包括没有显式指定为元字符的所有可打印和不可打印字符。这包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号。字面值字符：例如字母、数字、空格等，可以直接匹配它们自身。
- 元字符：元字符具有特殊的含义，例如 `\d` 匹配任意数字字符，`\w` 匹配任意字母数字字符，`.` 匹配任意字符（除了换行符）等。元字符：例如 `\d`、`\w`、`\s` 等，用于匹配特定类型的字符，如数字、字母、空白字符等。

  | 元字符 | 描述                                       | 等价于           |
  | :----- | :----------------------------------------- | :--------------- |
  | `.`    | 匹配除换行符 `\n` 外的任意单个字符         | `[^\n]`          |
  | `\d`   | 匹配任意数字                               | `[0-9]`          |
  | `\D`   | 匹配任意非数字                             | `[^0-9]`         |
  | `\w`   | 匹配字母、数字、下划线                     | `[A-Za-z0-9_]`   |
  | `\W`   | 匹配非字母、数字、下划线                   | `[^A-Za-z0-9_]`  |
  | `\s`   | 匹配任意空白字符（空格、制表符、换页符等） | `[ \f\n\r\t\v]`  |
  | `\S`   | 匹配任意非空白字符                         | `[^ \f\n\r\t\v]` |

​	`^`/`$`/`\`/

- 非打印字符

	 | \f    | 匹配一个换页符。等价于 \x0c 和 \cL。                         |
  | :---- | :----------------------------------------------------------- |
  | \n    | 匹配一个换行符。等价于 \x0a 和 \cJ。                         |
  | \r    | 匹配一个回车符。等价于 \x0d 和 \cM。                         |
  | \s    | 匹配任何空白字符，包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。注意 Unicode 正则表达式会匹配全角空格符。 |
  | \S    | 匹配任何非空白字符。等价于 `[^ \f\n\r\t\v]`。                |
  | \t    | 匹配一个制表符。等价于 \x09 和 \cI。                         |
  | \v    | 匹配一个垂直制表符。等价于 \x0b 和 \cK。                     |
  | `\cx` | 匹配由x指明的控制字符。例如， \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则，将 c 视为一个原义的 'c' 字符。 |

​	



**`.`** 特殊字符在中括号表达式时 如 **[.]** 只会匹配 **.**字符，等价于 **\\.**，而非匹配除换行符 **\n** 外的所有字符。

```
var str = "runoob.com";
var patt1 = /[.]/;
document.write(str.match(patt1));
```

### 量词（限定符）

用于指定匹配的次数或范围。

| 量词    | 描述                                    | 等价    |
| :------ | :-------------------------------------- | :------ |
| `*`     | 匹配前面的模式 **0次或多次**            | `{0,}`  |
| `+`     | 匹配前面的模式 **1次或多次**            | `{1,}`  |
| `?`     | 匹配前面的模式 **0次或1次**             | `{0,1}` |
| `{n}`   | 匹配前面的模式 **恰好 n 次**            |         |
| `{n,}`  | 匹配前面的模式 **至少 n 次**            |         |
| `{n,m}` | 匹配前面的模式 **至少 n 次，最多 m 次** |         |

- **`/[0-9]{1,2}/`**：匹配 0-99（包括 00, 01, 02... 以及 0 本身）
- **`/[1-9][0-9]?/`**：匹配 1-99（不包括 0，也不包括 00, 01 等前导零）

### 字符类

用方括号 `[ ]` 包围的字符集合，用于匹配方括号内的任意一个字符。

| 语法     | 描述                                 | 示例           |
| :------- | :----------------------------------- | :------------- |
| `[abc]`  | 匹配括号内的任意一个字符             | 匹配 a、b 或 c |
| `[^abc]` | 匹配除括号内字符外的任意字符         | 不匹配 a、b、c |
| `[A-Z]`  | 在字符类中表示范围 ,匹配所有大写字母 |                |
| `[a-z]`  | 匹配所有小写字母                     |                |
| `[0-9]`  | 匹配所有数字                         |                |
| `[\s\S]` | 匹配所有字符（包括换行符）           |                |

**^** 和 **[^指定字符串]** 之间的区别:

**^** 指的是匹配字符串开始的位置

**[^指定字符串]** 指的是除指定字符串以外的其他字符串

```
(^[0-9])+     //匹配有一至多个数字的字符串组合
[^[0-9]]+  // 匹配有一至多个不含数字的字符串组合
```





#### 正则表达式中 [] 和 () 的区别

圆括号 **()** 是组，主要应用在限制多选结构的范围/分组/捕获文本/环视/特殊模式处理。

示例：

-  1、**(abc|bcd|cde)**：表示这一段是abc、bcd、cde三者之一均可，顺序也必须一致。
-  2、**(abc)?**：表示这一组要么一起出现，要么不出现，出现则按此组内的顺序出现。
-  3、**(?:abc)**：表示找到这样abc这样一组，但不记录，不保存到$变量中，否则可以通过$x取第几个括号所匹配到的项，比如：(aaa)(bbb)(ccc)(?:ddd)(eee)，可以用 $1 获取 (aaa) 匹配到的内容，而 $3 则获取到了 (ccc) 匹配到的内容，而 $4 则获取的是由 (eee) 匹配到的内容，因为前一对括号没有保存变量。
-  4、**a(?=bbb)**：顺序环视 表示 a 后面必须紧跟 3 个连续的 b。
-  5、**(?i:xxxx)**：不区分大小写 (?s:.*) 跨行匹配.可以匹配回车符。

方括号 **[]** 是单个匹配，字符集/排除字符集/命名字符集。

示例：

-  1、**[0-3]**：表示找到这一个位置上的字符只能是 0 到 3 这四个数字，与 (abc|bcd|cde) 的作用比较类似，但圆括号可以匹配多个连续的字符，而一对方括号只能匹配单个字符。
-  2、**[^0-3]**：表示找到这一个位置上的字符只能是除了 0 到 3 之外的所有字符。

**() 和 [] 有本质的区别**

**()** 内的内容表示的是一个子表达式，() 本身不匹配任何东西，也不限制匹配任何东西，只是把括号内的内容作为同一个表达式来处理，例如 (ab){1,3}，就表示 ab 一起连续出现最少 1 次，最多 3 次。如果没有括号的话，ab{1,3} 就表示 a，后面紧跟的 b 出现最少 1 次，最多 3 次。另外，括号在匹配模式中也很重要。这个就不延伸了，有兴趣可以自己查查。

**[]** 表示匹配的字符在 [] 中，并且只能出现一次，并且特殊字符写在 [] 会被当成普通字符来匹配。例如 [(a)]，会匹配 (、a、)、这三个字符。



### 边界匹配（定位符）



位置匹配（也称为锚定或边界匹配）是指匹配字符串中的特定位置，而不是实际的字符。与普通字符匹配不同，位置匹配不消耗任何字符，它只是指定匹配必须发生的位置。

| 定位符 | 描述           | 说明                                                         |
| :----- | :------------- | :----------------------------------------------------------- |
| `^`    | 匹配字符串开头 | 如果设置了 RegExp 对象的 Multiline 属性,多行模式下也匹配 `\n` 或 `\r` 之后。当该符号在方括号表达式中使用时，表示不接受该方括号表达式中的字符集合。 |
| `$`    | 匹配字符串结尾 | 如果设置了 RegExp 对象的 Multiline 属性,多行模式下也匹配 `\n` 或 `\r` 之前. |
| `\b`   | 匹配单词边界   | 不占位置.单词边界是指`\w`（[a-zA-Z0-9_]）和`\W`之间的位置，或字符串的开始/结束位置。 |
| `\B`   | 匹配非单词边界 | 不占位置                                                     |

使用 `m` 标志（多行模式）改变 `^` 和 `$` 的行为：

默认情况下`^`和`$`匹配整个字符串的开头和结尾

多行模式下它们匹配每行的开头和结尾



**边界匹配不消耗字符**

其他位置匹配

`\A` 和 `\Z`（某些语言支持）

- `\A`：匹配字符串开头（不同于`^`，不受多行模式影响）
- `\Z`：匹配字符串结尾或结尾的换行符之前

### 分组和捕获

| 语法            | 描述                             |
| :-------------- | :------------------------------- |
| `(...)`         | 分组并捕获子表达式               |
| `(?:...)`       | 分组但不捕获子表达式（非捕获组） |
| `(?P<name>...)` | 命名捕获组（Python）             |
| `\1`、`\2`...   | 反向引用，引用前面捕获组的内容   |

关于反向引用， re 中每个括号对应一个引用下标，如果某个括号内的条件未匹配，则对应的返回值为 undefined。



### 选择符

| 语法 | 描述                                                   |
| :--- | :----------------------------------------------------- |
| `|`  | 匹配多个模式中的任意一个，如 `cat|dog` 匹配 cat 或 dog |



### 断言（环视）

| 语法       | 名称         | 描述                                 |
| :--------- | :----------- | :----------------------------------- |
| `(?=...)`  | 正向先行断言 | 后面必须匹配...                      |
| `(?!...)`  | 否定先行断言 | 后面不能匹配...                      |
| `(?<=...)` | 正向后行断言 | 前面必须匹配...（Python 不支持变长） |
| `(?<!...)` | 否定后行断言 | 前面不能匹配...（Python 不支持变长） |



### 特殊字符转义

需要转义的特殊字符：`. ^ $ * + ? { } [ ] \ | ( )`

`\`:转义字符，用于匹配特殊字符本身。

在字符前加反斜杠 `\` 即可匹配其字面意义，如 `\.` 匹配点号本身。



| 场景       | 要匹配的内容 | 正则表达式 | 示例文本                    |
| :--------- | :----------- | :--------- | :-------------------------- |
| JSON字符串 | 字面 `\n`    | `\\n`      | `{"text": "Hello\\nWorld"}` |
| 日志解析   | 换行符       | `\n`       | `2024-01-01\nError`         |

**？的作用：**

| 语法            | 名称               | 作用                        | 示例              |
| :-------------- | :----------------- | :-------------------------- | :---------------- |
| `?` 单独        | 量词               | 前面的字符出现 **0次或1次** | `colou?r`         |
| `?`             | 非贪婪量词         | 尽可能少匹配                | `.*?`             |
| `(?:...)`       | 非捕获组           | 分组但不保存                | `(?:abc)+`        |
| `(?=...)`       | 先行断言           | 后面必须是...               | `abc(?=123)`      |
| `(?!...)`       | 否定先行断言       | 后面不能是...               | `abc(?!123)`      |
| `(?<=...)`      | 后行断言           | 前面必须是...               | `(?<=abc)123`     |
| `(?<!...)`      | 否定后行断言       | 前面不能是...               | `(?<!abc)123`     |
| `(?P<name>...)` | 命名捕获组(Python) | 给分组命名                  | `(?P<year>\d{4})` |
| `(?<name>...)`  | 命名捕获组(其他)   | 给分组命名                  | `(?<year>\d{4})`  |
| `(?#...)`       | 注释               | 添加注释                    | `(?#这是注释)`    |





### 其它

| 特别字符 | 描述                                               |
| :------- | :------------------------------------------------- |
| [        | 标记一个中括号表达式的开始。要匹配 [，请使用 \\[。 |
| {        | 标记限定符表达式的开始。要匹配 {，请使用 \\{。     |



### 贪婪vs非贪婪

- **贪婪**：`*`、`+`、`{n,m}` 默认尽可能多匹配

- **非贪婪**：在量词后加 `?`，如 `*?`、`+?`、`{n,m}?`，尽可能少匹配

例如，您可能搜索 HTML 文档，以查找在 **h1** 标签内的内容。HTML 代码如下：

```
<h1>RUNOOB-菜鸟教程</h1>
```

**贪婪：**下面的表达式匹配从开始小于符号 (<) 到关闭 h1 标记的大于符号 (>) 之间的所有内容。

```
/<.*>/
```



**非贪婪：**如果您只需要匹配开始和结束 h1 标签，下面的非贪婪表达式只匹配 `<h1>`和`</h1>`。

```
/<.*?>/
```

也可以使用以下正则表达式来匹配 h1 标签，表达式则是：

```
/<\w+?>/
```

- `*?`：零次或多次，但尽可能少
- `+?`：一次或多次，但尽可能少
- `??`：零次或一次，但尽可能少
- `{n,m}?`：n到m次，但尽可能少

## 修饰符（标记）

正则表达式修饰符（也称为模式修饰符或标记）是用于改变正则表达式匹配行为的特殊指令。

标记也称为修饰符，正则表达式的标记用于指定额外的匹配策略。

标记不写在正则表达式里，标记位于表达式之外，格式如下：

```
/pattern/flags
```

下表列出了正则表达式常用的修饰符：

1. `i` (ignore case) - 忽略大小写

- 使匹配不区分大小写
- 示例：`/abc/i` 可以匹配 "abc", "Abc", "ABC" 等
- 支持语言：几乎所有正则表达式实现（JavaScript、PHP、Python等）

2. `g` (global) - 全局匹配

- 查找所有匹配项，而不是在第一个匹配后停止
- 示例：在字符串 "ababab" 中，`/ab/g` 会匹配所有三个 "ab"
- 支持语言：JavaScript、PHP等

3. `m` (multiline) - 多行模式

- 改变 `^` 和 `$` 的行为，使其匹配每行的开头和结尾，而不仅是整个字符串的开头和结尾
- 示例：在多行字符串中，`/^abc/m` 会匹配每行开头的 "abc"
- 支持语言：JavaScript、PHP、Python、Perl等

4. `s` (single line/dotall) - 单行模式

- 使点号 `.` 匹配包括换行符在内的所有字符
- 在JavaScript中称为"dotall"模式，使用 `/s` 修饰符
- 示例：`/a.b/s` 可以匹配 "a\nb"
- 支持语言：PHP、Perl、Python(作为`re.DOTALL`)、JavaScript(ES2018+)

5. `u` (unicode) - Unicode模式

- 启用完整的Unicode支持
- 正确处理UTF-16代理对和Unicode字符属性
- 示例：`/\p{Script=Greek}/u` 可以匹配希腊字母
- 支持语言：JavaScript、PHP等

6. `y` (sticky) - 粘性匹配

- 从目标字符串的当前位置开始匹配（使用`lastIndex`属性）
- 类似于`^`锚点，但针对的是匹配的起始位置
- 示例：在JavaScript中，`/a/y` 会从`lastIndex`开始匹配 "a"
- 支持语言：JavaScript

7. `x` (extended) - 扩展模式

- 忽略模式中的空白和注释，使正则表达式更易读
- 示例：在PHP中，`/a b c/x` 等同于 `/abc/`
- 支持语言：PHP、Perl、Python(作为`re.VERBOSE`)







## 定位符

定位符使您能够将正则表达式固定到行首或行尾。它们还使您能够创建这样的正则表达式，这些正则表达式出现在一个单词内、在一个单词的开头或者一个单词的结尾。

定位符用来描述字符串或单词的边界，**^** 和 **$** 分别指字符串的开始与结束，**\b** 描述单词的前或后边界，**\B** 表示非单词边界。

正则表达式的定位符有：

| 字符 | 描述                                                         |
| :--- | :----------------------------------------------------------- |
| ^    | 匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性，^ 还会与 \n 或 \r 之后的位置匹配。 |
| $    | 匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性，$ 还会与 \n 或 \r 之前的位置匹配。 |
| \b   | 匹配一个单词边界，即字与空格间的位置。                       |
| \B   | 非单词边界匹配。                                             |

**注意**：不能将限定符与定位符一起使用。由于在紧靠换行或者单词边界的前面或后面不能有一个以上位置，因此不允许诸如 **^\*** 之类的表达式。

若要匹配一行文本开始处的文本，请在正则表达式的开始使用 **^** 字符。不要将 **^** 的这种用法与中括号表达式内的用法混淆。

若要匹配一行文本的结束处的文本，请在正则表达式的结束处使用 **$** 字符。

若要在搜索章节标题时使用定位点，下面的正则表达式匹配一个章节标题，该标题只包含两个尾随数字，并且出现在行首：

```
/^Chapter [1-9][0-9]{0,1}/
```

真正的章节标题不仅出现行的开始处，而且它还是该行中仅有的文本。它既出现在行首又出现在同一行的结尾。下面的表达式能确保指定的匹配只匹配章节而不匹配交叉引用。通过创建只匹配一行文本的开始和结尾的正则表达式，就可做到这一点。

```
/^Chapter [1-9][0-9]{0,1}$/
```

匹配单词边界稍有不同，但向正则表达式添加了很重要的能力。单词边界是单词和空格之间的位置。非单词边界是任何其他位置。下面的表达式匹配单词 Chapter 的开头三个字符，因为这三个字符出现在单词边界后面：

```
/\bCha/
```

**\b** 字符的位置是非常重要的(**单词边界**).

\b 匹配的位置：
- 位置0（字符串开头）
- 位置6（字符串结尾）
- 任何 \w 和 \W 之间的位置(空格、`>`、换行等)

如果它位于要匹配的字符串的开始，它在单词的开始处查找匹配项。如果它位于字符串的结尾，它在单词的结尾处查找匹配项。例如，下面的表达式匹配单词 Chapter 中的字符串 ter，因为它出现在单词边界的前面：

```
/ter\b/
```

下面的表达式匹配 Chapter 中的字符串 apt，但不匹配 aptitude 中的字符串 apt：

```
/\Bapt/
```

字符串 apt 出现在单词 Chapter 中的非单词边界处，但出现在单词 aptitude 中的单词边界处。对于 **\B** 非单词边界运算符，不可以匹配单词的开头或结尾，如果是下面的表达式，就不匹配 Chapter 中的 Cha：

```
\BCha
```





## 断言

断言（Assertion）是正则表达式中用于指定匹配位置的元字符，它们不匹配任何实际字符，而是匹配字符之间的位置。

**断言的特点**

1. **零宽度**：不占用匹配字符的位置
2. **条件检查**：只检查是否满足特定条件
3. **不影响匹配结果**：仅作为匹配的约束条件

正则表达式中的零宽断言是一种特殊的结构，它在匹配的时候不会消耗字符，只是对**匹配位置**进行条件判断。这对于一些复杂的模式匹配非常有用，因为它允许你在匹配位置前面或后面添加条件，从而更精确地控制匹配。用于限定它前后的表达式，不能单独使用，本身没有作用。

正则表达式的先行断言和后行断言一共有 4 种形式：

- **(?=pattern)** 零宽正向先行断言(zero-width positive lookahead assertion)
- **(?!pattern)** 零宽负向先行断言(zero-width negative lookahead assertion)
- **(?<=pattern)** 零宽正向后行断言(zero-width positive lookbehind assertion)
- **(?<!pattern)** 零宽负向后行断言(zero-width negative lookbehind assertion)

| 断言类型         | 正则语法       | 别称   | 检查方向     | 期望条件             | 断言解释                                |
| :--------------- | :------------- | :----- | :----------- | :------------------- | :-------------------------------------- |
| **正向先行断言** | `(?=pattern)`  | 正前瞻 | 向右（向前） | **存在** `pattern`   | 我要找的位置，它的**右边必须**是...     |
| **负向先行断言** | `(?!pattern)`  | 负前瞻 | 向右（向前） | **不存在** `pattern` | 我要找的位置，它的**右边一定不能**是... |
| **正向后行断言** | `(?<=pattern)` | 正后顾 | 向左（向后） | **存在** `pattern`   | 我要找的位置，它的**左边必须**是...     |
| **负向后行断言** | `(?<!pattern)` | 负后顾 | 向左（向后） | **不存在** `pattern` | 我要找的位置，它的**左边一定不能**是... |

**所有正则引擎的扫描方向都是从左到右的**

lookaround 指前后看而不是“预查”，意为作用于前后表达式，即 lookahead(指向前看而不是“先行”)和 lookbehind(指向后看而不是“后发”)的合称；assert 指判断而不是“断言”；Positive 和 Negative 指肯否定而不是正负。

这里面的 **pattern** 是一个正则表达式。

如同 **^** 代表开头，**$** 代表结尾，**\b** 代表单词边界一样，先行断言和后行断言也有类似的作用，它们只**匹配某些位置，**在匹配过程中，不占用字符，所以被称为**"零宽"**。所谓位置，是指字符串中(每行)第一个字符的左边、最后一个字符的右边以及相邻字符的中间（假设文字方向是头左尾右）。



**概念说明：**

- 零宽（Zero-width）： 只匹配位置，零宽意味着断言在匹配时不会"消耗"字符串，匹配宽度为0.它只是对位置进行条件判断，但不把条件里的字符放进匹配结果中。
- 先行（Lookahead）： 表示断言发生在匹配位置之后。**在引擎继续向前匹配之前，先进行断言判断**。在当前位置向右看，判断后面的内容	→ 向前看
- 后行（Lookbehind）： 表示断言发生在匹配位置之前。**在引擎匹配完当前位置之后，回头看之前的位置是否符合条件**。在当前位置向左看，判断前面的内容	← 向后（相对于引擎从左到右扫描因此是向后的）看
- 正向（Positive）： 匹配括号中的表达式，即断言所作的条件判断是肯定的，即只有当条件成立时，匹配才成功。
- 负向（Negative）： 不匹配括号中的表达式，即断言所作的条件判断是否定的，即只有当条件不成立时，匹配才成功。





**正向先行断言** 用于匹配这样一个位置：在这个位置之后（右边），必须紧跟着出现指定的模式 `...`。

语法与参数

- **语法**：`(?=pattern)`
- **作用**：检查当前位置右侧是否匹配 `pattern`。如果匹配，则断言成功，引擎会回到当前位置继续后续匹配。
- **关键特性**：**零宽度**，即它只检查，不"吃掉"任何字符。`pattern` 中的内容不会成为最终匹配结果的一部分。





**负向先行断言** 与正向先行断言相反。它匹配一个位置，在这个位置之后（右边），**不能**紧跟着出现指定的模式 `...`。

语法与参数

- **语法**：`(?!pattern)`
- **作用**：检查当前位置右侧是否**不匹配** `pattern`。如果不匹配，则断言成功。
- **关键特性**：同样是零宽度。



**正向后行断言** 用于匹配一个位置，在这个位置之前（左边），必须紧挨着出现指定的模式 `...`。

*注意：不是所有编程语言的正则引擎都支持后行断言，JavaScript 在 ES2018 后才完全支持，而 Python 的 `re` 模块支持。*

语法与参数

- **语法**：`(?<=pattern)`
- **作用**：检查当前位置左侧是否匹配 `pattern`。如果匹配，则断言成功。
- **关键特性**：零宽度。`pattern` 必须有固定长度（不能是 `*` 或 `+` 等可变长度量词，在某些实现中）。



之所以叫后行断言，是因为正则表达式引擎在匹配字符串和表达式时，是从前向后逐个扫描字符串中的字符，并判断是否与表达式符合，当在表达式中遇到该断言时，正则表达式引擎需要往字符串前端检测已扫描过的字符，相对于扫描方向是向后的。



都是从前向后，但是先行是先取，然后往后断言

后行是先断言，然后后取。



**负向后行断言** 是正向后行断言的反面。它匹配一个位置，在这个位置之前（左边），**不能**紧挨着出现指定的模式 `...`。

语法与参数

- **语法**：`(?<!pattern)`
- **作用**：检查当前位置左侧是否**不匹配** `pattern`。
- **关键特性**：零宽度，通常要求 `pattern` 为固定长度。

![img](https://www.runoob.com/wp-content/uploads/2025/06/regexp-assertions-runoob-scaled-1.png)

该图展示了正则表达式引擎在匹配时如何处理断言。核心在于，**断言是一个独立的检查节点**。引擎在主表达式尝试匹配的**当前位置**，根据断言类型，向左或向右检查上下文。只有所有断言条件都满足，匹配才能继续向下进行，并"消耗"字符；否则，引擎会回溯尝试其他可能性或宣告匹配失败。



- **1、关于先行(lookahead)和后行(lookbehind)：**正则表达式引擎在执行字符串和表达式匹配时，会从头到尾（从前到后）连续扫描字符串中的字符，设想有一个扫描指针指向字符边界处并随匹配过程移动。先行断言，是当扫描指针位于某处时，引擎会尝试匹配指针还未扫过的字符（即断言），先于指针到达该字符，故称为先行。后行断言，引擎会尝试匹配指针已扫过的字符，后于指针到达该字符，故称为后行。
- **2、关于正向(positive)和负向(negative)：**正向就表示匹配括号中的表达式，负向表示不匹配。



上述 4 种断言，括号里的 pattern 本身是一个正则表达式。但对 2 种后行断言有所限制，在 Perl 和 Python 中，这个表达式必须是定长(fixed length)的，即不能使用 ***、+、?** 等元字符，如 **(?<=abc)** 没有问题，但 **(?<=a\*bc)** 是不被支持的，特别是当表达式中含有|连接的分支时，各个分支的长度必须相同。之所以不支持变长表达式，是因为当引擎检查后行断言时，无法确定要回溯多少步。Java 支持 **?、{m}、{n,m}** 等符号，但同样不支持 ***、+** 字符。Javascript 干脆不支持后行断言。



| 语言           | 后行断言支持 | 变长支持   | 说明                               |
| :------------- | :----------- | :--------- | :--------------------------------- |
| **Python**     | ✅ 支持       | ❌ 仅定长   | `(?<=abc)` 可以，`(?<=a+b)` 不行   |
| **JavaScript** | ✅ ES2018+    | ✅ 支持变长 | 现代浏览器都支持                   |
| **Java**       | ✅ 支持       | ⚠️ 有限支持 | 支持 `?`、`{n,m}`，不支持 `*`、`+` |
| **PHP/PCRE**   | ✅ 支持       | ❌ 仅定长   | 同 Python                          |
| **C#**         | ✅ 支持       | ✅ 支持变长 | .NET 框架支持                      |
| **Ruby**       | ✅ 支持       | ✅ 支持变长 | Onigmo 引擎                        |
| **Go**         | ❌ 不支持     | -          | 标准库不支持                       |

方案：不使用后顾断言，改用捕获组



```python
import re

text = "aabbbccc123"
# 用捕获组，然后取第二组
match = re.search(r'(a+b+c+)(\d+)', text)
if match:
    print(match.group(2))  # 输出: 123
```



## 选择，捕获， 分组和引用

用圆括号 **()** 将所有选择项括起来，相邻的选择项之间用 **|** 分隔。

**()** 表示捕获分组，**()** 会把每个分组里的匹配的值保存起来， 多个匹配值可以通过数字 n 来查看(**n** 是一个数字，表示第 n 个捕获组的内容)。



但用圆括号会有一个副作用，使相关的匹配会被缓存，此时可用 **?:** 放在第一个选项前来消除这种副作用。

其中 **?:** 是非捕获元之一，还有两个非捕获元是 **?=** 和 **?!**，这两个还有更多的含义，前者为正向预查，在任何开始匹配圆括号内的正则表达式模式的位置来匹配搜索字符串，后者为负向预查，在任何开始不匹配该正则表达式模式的位置来匹配搜索字符串。

在正则表达式中，**分组**（Grouping）允许我们将多个字符视为一个整体单元，就像数学中的括号一样。分组主要有两个作用：

1. **将多个字符作为一个整体**：可以对这个整体应用量词（如 `*`、`+`、`?`、`{n}`）
2. **捕获匹配的内容**：可以在后续引用或提取这部分匹配的内容

### 基本语法

使用圆括号 `()` 来创建分组：

```
(表达式)
```

例如，`(ab)+` 可以匹配 "ab"、"abab"、"ababab" 等，但不能匹配 "a" 或 "b"。

正则表达式中有几种不同类型的分组：



1. 捕获分组（Capturing Group）

最常见的分组形式，会捕获匹配的内容并分配一个编号（从1开始）。

(\d{4})-(\d{2})-(\d{2}) # 匹配日期格式 YYYY-MM-DD

这个表达式会创建3个分组：

- 分组1：4位数字的年份
- 分组2：2位数字的月份
- 分组3：2位数字的日期



2. 非捕获分组（Non-capturing Group）

使用 `(?:表达式)` 语法，表示只分组但不捕获。



(?:Mr|Ms|Mrs)\. (\w+) # 匹配 "Mr. Smith" 但只捕获 "Smith"

3. 命名分组（Named Capturing Group）

为分组指定名称，提高可读性（不同语言语法可能不同）。

python:

`(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})`

JS:

`(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})`





### 分组引用

分组最强大的功能之一是可以在正则表达式内部或外部引用已匹配的内容。

1. 反向引用（Backreference）

在正则表达式内部引用前面的分组，使用 `\数字` 语法：

(\w+) \1 # 匹配重复的单词，如 "hello hello"

这个模式会匹配两个相同的单词，中间用空格分隔。

2. 命名反向引用

对于命名分组，可以使用名称来引用：

```text
(?P<word>\w+) (?P=word) # Python 语法

\k<word>         # JavaScript 语法
```



###  替换引用

在替换操作中引用分组内容：

Python 示例：

```python
import re
text = "2023-05-15"
new_text = re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\2/\3/\1', text)
# 结果: "05/15/2023"
```



### 例子

这个模式可以匹配成对的HTML标签（如 `<div>...</div>`），其中：

```txt
<([a-z][a-z0-9]*)\b[^>]*>.*?</\1>
```

| 部分               | 含义                                                      |
| :----------------- | :-------------------------------------------------------- |
| `<`                | 匹配左尖括号                                              |
| `([a-z][a-z0-9]*)` | **捕获组1**：匹配标签名（首字符 a-z，后续可跟字母或数字） |
| `\b`               | 单词边界，确保标签名结束                                  |
| `[^>]*`            | 匹配标签属性（任意非 `>` 字符）                           |
| `>`                | 匹配右尖括号                                              |
| `.*?`              | 非贪婪匹配标签内的任意内容                                |
| `</\1>`            | 匹配结束标签，`\1` 引用捕获组1的标签名                    |

**验证重复单词**

\b(\w+)\b\s+\1\b

- `\b` - 单词边界
- `(\w+)` - 捕获组，匹配一个或多个单词字符（字母、数字、下划线）
- `\b` - 单词边界
- `\s+` - 一个或多个空白字符（空格、制表符、换行等）
- `\1` - 反向引用，匹配与第一个捕获组完全相同的内容
- `\b` - 单词边界



**反向引用**

对一个正则表达式模式或部分模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中，所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始，最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 **\n** 访问，其中 n 为一个标识特定缓冲区的一位或两位十进制数。

可以使用非捕获元字符 **?:**、**?=** 或 **?!** 来重写捕获，忽略对相关匹配的保存。

反向引用的最简单的、最有用的应用之一，是提供查找文本中两个相同的相邻单词的匹配项的能力。以下面的句子为例：

```
Is is the cost of of gasoline going up up?
```

上面的句子很显然有多个重复的单词。如果能设计一种方法定位该句子，而不必查找每个单词的重复出现，那该有多好。下面的正则表达式使用单个子表达式来实现这一点：

查找重复的单词：

```text
var str = "Is is the cost of of gasoline going up up";
var patt1 = /\b([a-z]+) \1\b/igm;
document.write(str.match(patt1));
```



捕获的表达式，正如 **[a-z]+** 指定的，包括一个或多个字母。正则表达式的第二部分是对以前捕获的子匹配项的引用，即，单词的第二个匹配项正好由括号表达式匹配。**\1** 指定第一个子匹配项。

单词边界元字符确保只检测整个单词。否则，诸如 "is issued" 或 "this is" 之类的词组将不能正确地被此表达式识别。

正则表达式后面的全局标记 **g** 指定将该表达式应用到输入字符串中能够查找到的尽可能多的匹配。

表达式的结尾处的不区分大小写 **i** 标记指定不区分大小写。

多行标记 **m** 指定换行符的两边可能出现潜在的匹配。

反向引用还可以将通用资源指示符 (URI) 分解为其组件。假定您想将下面的 URI 分解为协议（ftp、http 等等）、域地址和页/路径：

```
https://www.runoob.com:80/html/html-tutorial.html
```

下面的正则表达式提供该功能：

```text
var str = "https://www.runoob.com:80/html/html-tutorial.html";
var patt1 = /(\w+):\/\/([^/:]+)(:\d*)?([^# ]*)/;
arr = str.match(patt1);
for (var i = 0; i < arr.length ; i++) {
    document.write(arr[i]);
    document.write("<br>");
}
```

第三行代码 **str.match(patt1)** 返回一个数组，实例中的数组包含 5 个元素，索引 0 对应的是整个字符串，索引 1 对应第一个匹配符（括号内），以此类推。

第一个括号子表达式捕获 Web 地址的协议部分。该子表达式匹配在冒号和两个正斜杠前面的任何单词。

第二个括号子表达式捕获地址的域地址部分。子表达式匹配非 **:** 和 **/** 之后的一个或多个字符。

第三个括号子表达式捕获端口号（如果指定了的话）。该子表达式匹配冒号后面的零个或多个数字。只能重复一次该子表达式。

最后，第四个括号子表达式捕获 Web 地址指定的路径和 / 或页信息。该子表达式能匹配不包括 # 或空格字符的任何字符序列。

将正则表达式应用到上面的 URI，各子匹配项包含下面的内容：

- 第一个括号子表达式包含 **https**
- 第二个括号子表达式包含 `www.runoob.com`
- 第三个括号子表达式包含 **:80**
- 第四个括号子表达式包含 **/html/html-tutorial.html**

### 分组的高级应用

1. 条件匹配

有些正则引擎支持基于分组的条件匹配：

(?(1)true-pattern|false-pattern)

表示如果分组1已匹配，则匹配 true-pattern，否则匹配 false-pattern。

2. 平衡组（高级特性）

用于匹配嵌套结构（如括号），需要特定正则引擎支持。

| 概念         | 语法                         | 用途                   |
| :----------- | :--------------------------- | :--------------------- |
| 捕获分组     | `(pattern)`                  | 捕获匹配内容并分配编号 |
| 非捕获分组   | `(?:pattern)`                | 分组但不捕获           |
| 命名分组     | `(?P<name>pattern)` (Python) | 为分组指定名称         |
| 反向引用     | `\1`, `\2` 等                | 引用前面匹配的分组     |
| 命名反向引用 | `(?P=name)` (Python)         | 按名称引用分组         |
| 替换引用     | `$1`, `$2` 或 `\1`, `\2`     | 在替换字符串中引用分组 |



## 运算符优先级



正则表达式从左到右进行计算，并遵循优先级顺序，这与算术表达式非常类似。

相同优先级的从左到右进行运算，不同优先级的运算先高后低。下表从最高到最低说明了各种正则表达式运算符的优先级顺序：

| 运算符                      | 描述                                                         |
| :-------------------------- | :----------------------------------------------------------- |
| \                           | 转义符                                                       |
| (), (?:), (?=), []          | 圆括号和方括号                                               |
| *, +, ?, {n}, {n,}, {n,m}   | 限定符                                                       |
| ^, $, \任何元字符、任何字符 | 定位点和序列（即：位置和顺序）                               |
| \|                          | 替换，"或"操作 字符具有高于替换运算符的优先级，使得"m\|food"匹配"m"或"food"。若要匹配"mood"或"food"，请使用括号创建子表达式，从而产生"(m\|f)ood"。 |

以下是一些常见正则表达式运算符按照优先级从高到低的顺序：

- **转义符号：** `\` 是用于转义其他特殊字符的转义符号。它具有最高的优先级。

  示例：`\d`、`\.` 等，其中 `\d` 匹配数字，`\.` 匹配点号。

- **括号：** 圆括号 `()` 用于创建子表达式，具有高于其他运算符的优先级。

  示例：`(abc)+` 匹配 "abc" 一次或多次。

- **量词：** 量词指定前面的元素可以重复的次数。

  示例：`a*` 匹配零个或多个 "a"。

- **字符类：** 字符类使用方括号 `[]` 表示，用于匹配括号内的任意字符。

  示例：`[aeiou]` 匹配任何一个元音字母。

- **断言：** 断言是用于检查字符串中特定位置的条件的元素。

  示例：`^` 表示行的开头，`$` 表示行的结尾。

- **连接：** 连接在没有其他运算符的情况下表示字符之间的简单连接。

  示例：`abc` 匹配 "abc"。

- **管道：** 管道符号 `|` 表示"或"关系，用于在多个模式之间选择一个。

  示例：`cat|dog` 匹配 "cat" 或 "dog"。

接下来我们看下以下正则表达式的优先级说明：

```
\d{2,3}|[a-z]+(abc)*
```

- `\d{2,3}` 匹配两到三个数字。
- `|` 表示或。
- `[a-z]+` 匹配一个或多个小写字母。
- `(abc)*` 匹配零个或多个 "abc"。



## 选择和分支

1、什么是选择 (Alternation)

选择（Alternation）是指在正则表达式中使用 **`|`** 符号表示 "或" 的逻辑关系。它允许你匹配多个可能的模式之一。

```
cat|dog
```

这个模式会匹配 "cat" 或 "dog"。

2、什么是分支 (Branching)

分支是指正则表达式引擎在匹配过程中遇到选择点时，会尝试不同的匹配路径。当一条路径匹配失败时，引擎会回溯并尝试其他可能的路径。



选择操作符 `|` 的工作原理

1. 正则表达式引擎从左到右扫描 **`|`** 分隔的各个选项
2. 尝试匹配第一个选项，如果成功则停止
3. 如果第一个选项不匹配，则尝试第二个选项
4. 依此类推，直到找到匹配或所有选项都尝试完毕

分组与选择

选择通常与分组 **`()`** 结合使用，以限定选择的范围：

实例

gr(a|e)y

这个模式会匹配 "gray" 或 "grey"。

分支回溯机制

当正则表达式引擎遇到选择点时：

1. 记住当前位置（创建检查点）
2. 尝试第一个分支
3. 如果失败，回退到检查点
4. 尝试下一个分支
5. 重复直到成功或所有分支都尝试过



性能优化技巧

1. **高频选项前置**：将最可能匹配的选项放在前面

   ```
   (common|uncommon|rare)  # 优化顺序
   ```

2. **避免冗余分支**：

   ```
   (a|ab|c)  # 不好
   (ab?|c)   # 更好
   ```

3. **使用非捕获组**：当不需要捕获分组时

   ```
   (?:pattern1|pattern2)
   ```

## 历史

1951 年, 一位叫 Stephen Kleene 的数学家在 McCulloch 和 Pitts 早期工作的基础上，发表了一篇标题为《神经网事件的表示法》的论文，引入了正则表达式的概念。正则表达式就是用来描述他称为**正则集的代数**的表达式，因此采用**正则表达式**这个术语。

随后，发现可以将这一工作应用于使用 Ken Thompson 的计算搜索算法的一些早期研究，Ken Thompson 是 Unix 的主要发明人。正则表达式的第一个实用应用程序就是 Unix 中的 grep 编辑器。

大致发展历史如下：

- 1951年：计算理论的奠基人之一，美国计算机科学家Stephen Kleene首次提出了正则语言的概念，并使用形式化的方法来描述这种语言。这为正则表达式的发展奠定了理论基础。
- 1960年代：Ken Thompson，Unix操作系统的共同创始人之一，开发了第一个实际应用正则表达式的程序，它是Unix中grep命令的一部分。这标志着正则表达式的实际应用。
- 1970年代：Ken Thompson和Rob Pike开发了第一个正则表达式引擎，在Unix系统中广泛使用，这对正则表达式的普及起到了关键作用。
- 80年代，POSIX (Portable Operating System Interface) 标准公诸于世，它制定了不同的操作系统都需要遵守的一套规则，其中就包括正则表达式的规则。遵循POSIX规则的正则表达式，称为POSIX派系的正则表达式。Unix 系统或类Unix系统上的大部分工具，如grep 、sed 、awk等都属于POSIX派系。
- 同样在80年代，Larry Wall发布了Perl编程语言，其中引入的正则表达式功能是颗耀眼明珠。
- 1997年：**Philip Hazel 开发了 PCRE 库**，旨在将 Perl 风格的正则表达式能力带给其他编程语言
- 1997年：IEEE发布了POSIX.2标准，其中包括了正则表达式的标准规范，这使得正则表达式在不同Unix系统中的行为更加一致。
- 2000年代以后：正则表达式在计算机编程和文本处理中变得越来越流行，支持正则表达式的编程语言和工具变得更加丰富和强大，如Perl、Python、Java、JavaScript等。

| 编程语言       | 支持方式                                  | 常用类/模块                         | 简单示例（匹配数字）                           |
| :------------- | :---------------------------------------- | :---------------------------------- | :--------------------------------------------- |
| **JavaScript** | 语言原生支持                              | `RegExp` 对象，或使用字面量 `/.../` | `/\d+/` 或 `new RegExp("\\d+")`                |
| **Python**     | 标准库 `re` 模块                          | `re` 模块                           | `re.compile(r"\d+")`                           |
| **Java**       | 标准库 `java.util.regex` 包               | `Pattern` 和 `Matcher` 类           | `Pattern.compile("\\d+")`                      |
| **PHP**        | 内置 PCRE 函数                            | `preg_` 系列函数（如 `preg_match`） | `preg_match("/\d+/", $text)`                   |
| **C#**         | `System.Text.RegularExpressions` 命名空间 | `Regex` 类                          | `new Regex(@"\d+")`                            |
| **Go**         | 标准库 `regexp` 包                        | `regexp` 包                         | `regexp.MustCompile(`\d+`)`                    |
| **Ruby**       | 语言原生支持，核心类                      | `Regexp` 类                         | `/\d+/`                                        |
| C++            | C++ 标准库 (`std::regex`)                 | `<regex>`                           | `std::regex("\\d+")` 或 `std::regex(R"(\d+)")` |
| C              | POSIX库                                   | `<regex.h>`                         | regcomp(&regex, "[0-9]+", REG_EXTENDED)        |
| **Rust**       | 标准库（需 crate）                        | `regex` crate                       | `Regex::new(r"\d+").unwrap()`                  |
| **Perl**       | 语言原生                                  | 直接嵌入                            | `$text =~ /\d+/`                               |
| **SQL**        | 数据库扩展                                | 各数据库不同                        | MySQL: `REGEXP '[0-9]+'`                       |

### POSIX派系

遵循POSIX规则的正则表达式其中代表软件有：grep ，sed和awk等

**BRE和ERE标准**

POSIX派系分为两种标准：

1. BRE标准（Basic Regular Expression基本正则表达式）
2. ERE标准（Extended Regular Expression扩展正则表达式）

### **PCRE派系**

现代编程语言大部分都属于PCRE派系，如Python，PHP和Java 等。

### **PCRE与Perl**

- Perl1提供了正则表达式操作符——是通用脚本语言的首创
- Perl2补充/i量词，能够进行不区分大小写匹配等
- Perl3支持/e量词，能够增强替换运算符的能力；{min,max}区间量词等
- Perl5 添加 非捕获的括号，忽略优先的量词，顺序环视功能等
