DC - 正则表达式

# 2.14. 正则表达式

## 目录

1. 简介
2. 简单匹配
3. 转义序列
4. 字符类
5. 元字符
  5.1. 元字符 - 行分隔
  5.2. 元字符 - 预定义类
  5.3. 元字符 - 单词边界
  5.4. 元字符 - 量词
  5.5. 元字符 - 备选项
  5.6. 元字符 - 子表达式
  5.7. 元字符 - 反向引用
6. 断言（前瞻与后顾）
7. 非捕获分组
8. 原子分组
9. Unicode 类别
10. 修饰符

Double Commander 使用 Andrey Sorokin 的免费库 TRegExpr。

本页大部分说明摘自该库的帮助文档。

## 1. 简介

正则表达式（Regular Expressions）是一种广泛使用的文本模式匹配方法。特殊字符（元字符）允许我们指定，例如要查找的字符串位于行首或行尾，或包含某一字符或字符组的 n 次重复。

Double Commander 在以下功能中支持正则表达式：

- 搜索文件（按文件名或内容搜索）
- 在 批量重命名工具 中
- 在内置编辑器中
- 在内置查看器中

TRegExpr 库支持两种工作模式：ANSI 与 Unicode。对文本文件的搜索会根据文件编码使用其中一种或两种模式；按名称搜索时使用 Unicode。

（回到顶部）

## 2. 简单匹配

任何单个字符通常与其自身匹配，除非该字符是下面所述的具有特殊含义的元字符。

一系列字符会匹配目标字符串中的相同字符序列，例如模式 `bluh` 会匹配目标字符串中的 `bluh`。

我们可以通过在前面加反斜杠 `\` 来对通常作为元字符或 转义序列 的字符进行转义，使其按字面含义解释，例如：元字符 `^` 匹配行首，但 `\^` 匹配字符 `^`，`\\` 匹配 `\`，以此类推。

示例：

简单匹配示例
表达式 | 说明
--- | ---
foobar | 匹配字符串 `foobar`
\^FooBarPtr | 匹配 `^FooBarPtr`

（回到顶部）

## 3. 转义序列

字符可以使用类似 C 或 Perl 的转义序列语法来表示：`\n` 匹配换行，`\t` 匹配制表符，等等。

更一般地，`\xnn`（其中 `nn` 为十六进制数）匹配 ASCII 值为 `nn` 的字符。

如果需要宽字符（Unicode）代码，可使用 `\x{nnnn}`，其中 `nnnn` 为一个或多个十六进制数字。

转义序列
表达式 | 说明
--- | ---
\xnn | 十六进制代码为 `nn` 的字符
\x{nnnn} | 十六进制代码为 `nnnn` 的字符（普通文本为一字节，Unicode 为两字节或更多）
\t | 制表符（TAB），等同于 `\x09`
\n | 换行（LF），等同于 `\x0a`
\r | 回车（CR），等同于 `\x0d`
\f | 换页（FF），等同于 `\x0c`
\a | 警报（BEL），等同于 `\x07`
\e | 转义（ESC），等同于 `\x1b`

示例：

转义序列示例
表达式 | 说明
--- | ---
foo\x20bar | 匹配 `foo bar`（中间的空格）
\tfoobar | 匹配以制表符开始的 `foobar`

（回到顶部）

## 4. 字符类

可以通过将字符列表用 `[]` 括起来来指定字符类，匹配列表中任意一个字符（仅匹配一个字符）。

若 `[` 后的第一个字符为 `^`，则表示匹配不在列表中的任意字符。

在列表内，字符 `-` 用来指定范围，例如 `a-z` 表示从 `a` 到 `z` 的所有字符（含首尾）。

如果想把 `-` 本身包含在类中，可将其放在列表开头或结尾，或用反斜杠转义。

若想包含 `]`，可将其放在列表开头或用反斜杠转义。

字符类
表达式 | 说明
--- | ---
[-az] | 匹配 `a`、`z` 和 `-`
[az-] | 匹配 `a`、`z` 和 `-`
[a\-z] | 匹配 `a`、`z` 和 `-`
[a-z] | 匹配从 `a` 到 `z` 的全部 26 个小写字母
[\n-\x0D] | 匹配例如 `\x10`、`\x11`、`\x12`、`\x13` 等字符
[\d-t] | 匹配任意数字、`-` 或 `t`
[]-a] | 匹配从 `]` 到 `a` 的任意字符

示例：

字符类示例
表达式 | 说明
--- | ---
foob[aeiou]r | 匹配 `foobar`、`foober` 等，但不匹配 `foobbr`、`foobcr` 等
foob[^aeiou]r | 匹配 `foobbr`、`foobcr` 等，但不匹配 `foobar`、`foober` 等

（回到顶部）

## 5. 元字符

元字符是正则表达式的核心，用于表示特殊含义。

下面将简介不同类型的元字符。

（回到顶部）

## 5.1. 元字符 - 行分隔

以下元字符用于检测行分隔位置。

行分隔
表达式 | 说明
--- | ---
^ | 行首
$ | 行尾
\A | 文本开始处
\Z | 文本结束处
. | 匹配行内任意字符

示例：

行分隔示例
表达式 | 说明
--- | ---
^foobar | 仅当 `foobar` 位于行首时匹配
foobar$ | 仅当 `foobar` 位于行尾时匹配
^foobar$ | 仅当该行仅包含 `foobar` 时匹配
foob.r | 匹配类似 `foobar`、`foobbr`、`foob1r` 等字符串

默认情况下，`^` 仅匹配输入文本的开始处，`$` 仅匹配文本的结尾。嵌入的行分隔符不会被 `^` 或 `$` 匹配。

如果希望将字符串视为多行缓冲区，使得 `^` 在任何行分隔符之后匹配，`$` 在任何行分隔符之前匹配，可通过开启 修饰符 m 来实现。

`\A` 与 `\Z` 类似于 `^` 与 `$`，但在使用 修饰符 m 时不会像 `^` 与 `$` 那样多次匹配内部行分隔处。

默认情况下，`.` 匹配任意字符，但若关闭 修饰符 s，则 `.` 不会匹配嵌入的行分隔符。

TRegExpr 按照 Unicode 技术规范（Technical Standard #18）处理行分隔：

- `^` 匹配输入字符串的开始处；若启用 修饰符 m，也匹配紧随 `\x0D\x0A`、`\x0A` 或 `\x0D` 之后的位置（在 Unicode 支持下也包括 `\x2028`、`\x2029`、`\x0B`、`\x0C` 和 `\x85`）。注意，在序列 `\x0D\x0A` 中不存在空行位置。

- `$` 匹配输入字符串的结束处；若启用 修饰符 m，也匹配紧接在 `\x0D\x0A`、`\x0A` 或 `\x0D` 之前的位置（在 Unicode 支持下也包括 `\x2028`、`\x2029`、`\x0B`、`\x0C` 和 `\x85`）。

- `.` 匹配任意字符，但若关闭 修饰符 s，则 `.` 不匹配 `\x0D\x0A`、`\x0A` 或 `\x0D`（在 Unicode 支持下也不匹配 `\x2028`、`\x2029`、`\x0B`、`\x0C` 和 `\x85`）。

注意，模式 `^.*$`（匹配空行）不会匹配序列 `\x0D\x0A` 中的空字符串，但会匹配序列 `\x0A\x0D` 中的空字符串。

（回到顶部）

## 5.2. 元字符 - 预定义类

下列元字符用于匹配某些字符组。

预定义类
表达式 | 说明
--- | ---
\w | 字母数字字符（包括 `_`），即 `[0-9A-Za-z_]`
\W | 非字母数字字符
\d | 数字字符
\D | 非数字字符
\s | 任意空白字符（等同于 `[ \t\n\r\f]`）
\S | 非空白字符

可以在自定义 字符类 中使用 `\w`、`\d` 和 `\s`。

示例：

预定义类示例
表达式 | 说明
--- | ---
foob\dr | 匹配 `foob1r`、`foob6r` 等，但不匹配 `foobar`、`foobbr` 等
foob[\w\s]r | 匹配 `foobar`、`foob r`、`foobbr` 等，但不匹配 `foob=r`

（回到顶部）

## 5.3. 元字符 - 单词边界

单词边界（`\b`）是两个字符之间的位置，一侧为 `\w`，另一侧为 `\W`（两种顺序均可）；字符串开头和结尾处的“虚拟字符”视为 `\W`。

单词边界
表达式 | 说明
--- | ---
\b | 匹配单词边界
\B | 匹配非单词边界的位置

（回到顶部）

## 5.4. 元字符 - 量词

正则表达式中的任何项都可以后跟称为量词的元字符，用以指定前一项出现的次数。

量词
表达式 | 说明
--- | ---
* | 零次或多次（贪婪），等同于 `{0,}`
+ | 一次或多次（贪婪），等同于 `{1,}`
? | 零次或一次（贪婪），等同于 `{0,1}`
{n} | 精确匹配 `n` 次（贪婪）
{n,} | 至少匹配 `n` 次（贪婪）
{n,m} | 至少匹配 `n` 次且不超过 `m` 次（贪婪）
*? | 零次或多次（非贪婪），等同于 `{0,}?`
+? | 一次或多次（非贪婪），等同于 `{1,}?`
?? | 零次或一次（非贪婪），等同于 `{0,1}?`
{n}? | 精确匹配 `n` 次（非贪婪）
{n,}? | 至少匹配 `n` 次（非贪婪）
{n,m}? | 至少匹配 `n` 次且不超过 `m` 次（非贪婪）

因此，花括号形式的 `{n,m}` 指定匹配项的最少（`n`）与最多（`m`）匹配次数。

`{n}` 等价于 `{n,n}`，表示精确匹配 `n` 次；`{n,}` 表示匹配至少 `n` 次。

理论上 `n` 与 `m` 的大小没有硬性上限，但过大的数字会降低执行速度并消耗更多内存。

若花括号出现在其它上下文中，则被视为普通字符。

示例：

量词示例
表达式 | 说明
--- | ---
foob.*r | 匹配 `foobar`、`foobalkjdflkj9r`、`foobr` 等
foob.+r | 匹配 `foobar`、`foobalkjdflkj9r` 等，但不匹配 `foobr`
foob.?r | 匹配 `foobar`、`foobbr`、`foobr`，但不匹配较长的 `foobalkj9r`
fooba{2}r | 匹配字符串 `foobaar`
fooba{2,}r | 匹配 `foobaar`、`foobaaar`、`foobaaaar` 等
fooba{2,3}r | 匹配 `foobaar` 或 `foobaaar`，但不匹配 `foobaaaar`

关于“贪婪（greedy）”与“非贪婪（non-greedy）”：

“贪婪”尽可能多地匹配，而“非贪婪”尽可能少地匹配。

例如，模式 `b+` 与 `b*` 应用于字符串 `abbbbc` 时会返回 `bbbb`；`b+?` 返回 `b`；`b*?` 返回空字符串；`b{2,3}?` 返回 `bb`；而 `b{2,3}` 返回 `bbb`。

可以通过 修饰符 g 将所有量词切换为“非贪婪”模式。

（回到顶部）

## 5.5. 元字符 - 备选项

可以使用 `|` 指定多个备选模式，例如 `fee|fie|foe` 会匹配 `fee`、`fie` 或 `foe` 中的任意一个（等价于 `f(e|i|o)e`）。

第一个备选项包含从上一个模式分隔符（如 `(`、`[` 或模式开始）到第一个 `|` 之间的内容，最后一个备选项则从最后一个 `|` 到下一个模式分隔符之间的内容。

因此通常用括号包裹备选项以避免歧义。

备选项按从左到右的顺序尝试，第一个能使整个表达式匹配的备选项将被选中。

这意味着备选项本身并不总是“贪婪”的。

例如，将 `foo|foot` 匹配到 `barefoot` 时，只会匹配到 `foo`，因为它是第一个被尝试且成功的备选项。这在使用括号捕获匹配文本时尤为重要。

另外请注意，在方括号内 `|` 被视为普通字符，例如 `[fee|fie|foe]` 实际上匹配的是字符集合 `[feio|]`。

示例：

备选项示例
表达式 | 说明
--- | ---
foo(bar|foo) | 匹配 `foobar` 或 `foofoo`

（回到顶部）

## 5.6. 元字符 - 子表达式

括号构造 `( ... )` 可用于定义子表达式。

搜索后可以引用任意子表达式，也可在替换模板中使用子表达式。

子表达式按其左括号在模式中的从左到右顺序编号。

第一个子表达式编号为 1，最多支持 90 个子表达式（整个正则表达式的匹配编号为 0，可用 `$0` 或 `$&` 代替）。

示例：

子表达式示例
表达式 | 说明
--- | ---
(foobar){8,10} | 匹配包含 8、9 或 10 个 `foobar` 实例的字符串
foob([0-9]|a+)r | 匹配 `foob0r`、`foob1r`、`foobar`、`foobaar` 等

关于“替换为”模板的说明：

- 若要在替换模板中插入字面 `$` 或 `\`，请在前面加上转义 `\`。
  例如：`1\$ is $2\\rub\\` 将产生 `1$ is <subexpr2>\rub\`。
- 若要在 `$n` 之后直接放置数字字符，必须用花括号 `{}` 将编号 `n` 定界。
  例如，`a$12bc` 表示子表达式 12，而 `a${1}2bc` 表示子表达式 1 后跟字符 `2`。

示例：

将日期 `21.01.2018` 反转为 `2018.01.21`：
查找：`(\d{2})\.(\d{2})\.(\d{4})`
替换为：`$3.$2.$1`

（回到顶部）

## 5.7. 元字符 - 反向引用

元字符 `\1` 到 `\9` 被解释为反向引用，`\n` 匹配之前编号为 `n` 的子表达式所匹配的文本。

示例：

反向引用示例
表达式 | 说明
--- | ---
(.)\1+ | 匹配 `aaaa` 和 `cc`
(.+)\1+ | 也匹配 `abab` 和 `123123`
(['"]?)(\d+)\1 | 匹配 `"13"`（双引号）、`'4'`（单引号）或 `77`（无引号）等

（回到顶部）

## 6. 断言（前瞻与后顾）

正向前瞻：`foo(?=bar)` 仅在 `foo` 之后跟随 `bar` 时匹配 `foo`，且 `bar` 不包含在匹配结果中。

负向前瞻：`foo(?!bar)` 仅在 `foo` 后不跟 `bar` 时匹配 `foo`。

正向后顾：`(?<=foo)bar` 仅在 `bar` 前面紧跟 `foo` 时匹配 `bar`，且 `foo` 不包含在匹配结果中。

负向后顾：`(?<!foo)bar` 仅在 `bar` 前不以 `foo` 为前缀时匹配 `bar`。

限制：

- 前瞻的括号必须出现在表达式的末尾，后顾的括号必须出现在表达式的开头。因此在选择项 `|` 之间或组内使用断言是不被支持的。

- 对于后顾断言 `(?<!foo)bar`，模式 `foo` 必须为固定长度，即仅包含固定长度的匹配操作。量词不被允许，除非使用固定重复次数的花括号 `{n}` 或 `{n,n}`。字符类允许使用，点号允许使用，`\b` 与 `\B` 也允许。分组与选择（groups/choices）不被允许。

- 对于另外三种断言类型（其它前瞻/后顾），括号内的表达式可以为任意复杂度。

（回到顶部）

## 7. 非捕获分组

语法：`(?:expr)`。

此类分组没有索引且对反向引用不可见。当只需对子表达式进行分组但不希望将其作为捕获部分保存时使用非捕获分组。使用非捕获分组可提升正则表达式的执行效率。

非捕获分组示例
表达式 | 说明
--- | ---
(https?|ftp)://([^/\r\n]+) | 在 `https://doublecmd.sourceforge.io` 中匹配 `https` 与 `doublecmd.sourceforge.io`
(?:https?|ftp)://([^/\r\n]+) | 在 `https://doublecmd.sourceforge.io` 中仅匹配 `doublecmd.sourceforge.io`

（回到顶部）

## 8. 原子分组

语法：`(?>expr|expr|...)`。

原子分组是非捕获分组的一种特殊形式：当分组内的某部分已匹配成功时，原子分组会禁止该分组的回溯，从而提高匹配速度，适用于包含多个备选项的组的优化。

例如，`a(bc|b)c` 会匹配 `abcc` 与 `abc`，而 `a(?>bc|b)c` 会匹配 `abcc` 但不匹配 `abc`，因为引擎禁止回溯尝试将组匹配为 `b`。

（回到顶部）

## 9. Unicode 类别

Unicode 标准为字符类别定义了名称，通常为 2 个字母的标识。例如 `Lu` 表示大写字母，`Ll` 表示小写字母；而 1 个字母的更大类 `L` 表示所有字母。

Unicode 类别
类别 | 说明
--- | ---
L | 字母
Lu | 大写字母
Ll | 小写字母
Lt | 标题字母
Lm | 修饰字母
Lo | 其他字母
M | 标记（Mark）
Mn | 非间隔标记
Mc | 间隔结合标记
Me | 包围标记
N | 数字
Nd | 十进制数字
Nl | 字母数字（数字类）
No | 其他数字
P | 标点
Pc | 连接符标点
Pd | 破折号标点
Ps | 开括号类标点
Pe | 闭括号类标点
Pi | 起始标点
Pf | 结束标点
Po | 其他标点
S | 符号
Sm | 数学符号
Sc | 货币符号
Sk | 修饰符号
So | 其他符号
Z | 分隔符
Zs | 空格分隔符
Zl | 行分隔符
Zp | 段落分隔符
C | 其他
Cc | 控制字符
Cf | 格式字符
Cs | 代理项
Co | 私有使用
Cn | 未分配

元字符 `\p` 表示属于指定类别的单个 Unicode 字符，语法有 `\pL` 或 `\p{L}`（单字符类别）以及 `\p{Lu}`（双字符类别）。

元字符 `\P` 表示不属于指定类别的字符（取反）。

这些元字符也可在字符类内部使用。

（回到顶部）

## 10. 修饰符

单个修饰符的语法为：`(?i)` 打开，`(?-i)` 关闭。也可以同时指定多个修饰符，例如 `(?msgxr-imsgxr)`。

修饰符用于改变正则表达式的行为。修饰符只影响出现在该修饰符之后的正则表达式部分。

这些修饰符可以内嵌在正则表达式中；若修饰符在子表达式内，则仅影响该子表达式。

`i`
  进行不区分大小写的匹配（使用系统区域设置）。默认关闭。
`m`
  将字符串视为多行，即使 `^` 与 `$` 匹配每行的开始/结束（见 行分隔）。默认关闭。
`s`
  将字符串视为单行，即 `.` 匹配包括行分隔在内的任意字符（见 行分隔）。默认开启。
`g`
  非标准修饰符：关闭该修饰符将把后续所有量词切换为非贪婪模式（默认该修饰符开启）。例如若 `g` 关闭，则 `+` 等同于 `+?`，`*` 等同于 `*?` 等。
`x`
  允许在模式中使用空白和注释以提高可读性（见下文）。默认关闭。
`r`
  非标准修饰符：开启后范围 `а-я` 额外包含俄语字母 `ё`，`А-Я` 包含 `Ё`，并且 `а-Я` 包含整个俄语字母表。默认开启。
`#`
  `(?#text)`：注释，`text` 被忽略。注意 TRegExpr 在遇到第一个 `)` 时就会结束注释，因此无法在注释中使用字面 `)`。

示例：

Perl 扩展示例
表达式 | 说明
--- | ---
(?i)Saint-Petersburg | 匹配 `Saint-petersburg` 和 `Saint-Petersburg`
(?i)Saint-(?-i)Petersburg | 匹配 `Saint-Petersburg`，但不匹配 `Saint-petersburg`
(?i)(Saint-)?Petersburg | 匹配 `Saint-petersburg` 与 `saint-petersburg`
((?i)Saint-)?Petersburg | 匹配 `saint-Petersburg` 但不匹配 `saint-petersburg`

修饰符 `x` 需要补充说明。

它会忽略非转义且不在字符类内的空白字符，从而允许将正则表达式拆分为更具可读性的部分。

`#` 字符在此模式下被视为注释起始，例如：

    (
      (abc) # 注释 1
        |   # 可以用空格美化正则 - TRegExpr 会忽略它
      (efg) # 注释 2
    )

这也意味着如果你需要在模式中使用字面空白或 `#`（在字符类外，这些字符会被 `x` 影响），则必须对它们进行转义或用八进制/十六进制转义表示。

综上所述，这些特性有助于提高正则表达式的可读性。

（回到顶部）