正则表达式——全面指南
目录
1. 简介
文本——无论是日志文件、配置数据、用户输入还是文档——在编程中无处不在。从简单的脚本到完整的应用,开发者经常需要搜索、验证、提取或转换文本片段。虽然许多 IDE 和编辑器(如 Notepad++、VS Code 或 Sublime Text)提供了基本的查找和替换功能,但当你需要处理模式、条件逻辑或重复结构时,这些功能很快就会捉襟见肘。这正是正则表达式(regex)的用武之地。
从本质上讲,正则表达式是一种描述一组字符串的紧凑、声明式的方式。比如说,你不必手动逐行扫描文本来找出日志中的所有警告消息,而是可以编写一个模式,匹配时间戳、日志级别、文件名、电子邮件地址、URL——任何你能想到的内容。一旦理解了基础知识,正则表达式就可能成为你开发者工具箱中最强大的“武器”之一。
来看一段典型的日志:
2025-05-31 14:23:48 [Info] Starting application...
2025-05-31 14:23:50 [Info] Loading application data...
2025-05-31 14:23:55 [Warning] Missing font file: New Times Roman.
2025-05-31 14:24:22 [Info] The application has started, with 1 warning.
2025-05-31 14:25:01 [Error] Unhandled exception: NullReferenceException.
如果你想回答这样的问题:
- “发出了哪些警告,分别在什么时候?”
- “哪些行包含错误消息?”
- “应用启动了多少次?”
你可以用自己喜欢的语言编写一个脚本,按空格拆分、检查各个标记并进行字符串比较。但这很快就会变得繁琐:时间戳格式的多变、可选字段的处理、空白字符的各种怪异情况,都可能让你的代码变得脆弱。
然而,借助一个精心编写的正则表达式,你可以匹配整行日志,并捕获时间戳、日志级别以及消息的其余部分——全部在一个模式中完成。更重要的是,正则表达式可以处理任何具有一致结构的基于文本的格式:CSV 文件、HTML/XML 标签、自定义配置格式,等等。
读完本文后,你将:
- 理解正则表达式背后的基本动机。
- 了解适用于日常任务的实用语法元素和示例。
- 拥有一份汇总最常用正则表达式结构的便捷参考。
无论你只是在编辑器中做过一些“查找和替换”,还是编写过简单的字符串处理代码,本指南都将帮你跨越这道鸿沟,并向你展示如何借助正则表达式更上一层楼。
2. 在线测试器
替换(可选)
3. 实用语法
本节将介绍正则表达式模式的核心构建块。每个小节先介绍一个概念,然后给出真实场景中的示例——有些基于日志处理,有些则涉及验证电子邮件地址或从文本中提取数字等日常任务。
3.1 字面字符与简单匹配
在最简单的层面上,正则表达式可以只由字面字符组成。例如:
- 模式
Error会匹配文本中出现的任何子字符串“Error”。 - 模式
2025-05-31会匹配这个确切的日期字符串。
然而,真实世界中的文本很少能完美对齐。例如,日志消息中包含不断变化的时间戳和不同的日志级别。要以通用的方式匹配它们,你需要用到元字符。
要点: 正则表达式中的大多数字符都按字面匹配其自身,特殊的元字符(如
.、*、?、\等)除外。要匹配字面意义上的句点、方括号或其他元字符,你必须用反斜杠对其进行“转义”(用\.匹配字面的点,用\[匹配字面的左方括号,依此类推)。
3.2 字符类([])与范围
字符类是用方括号括起来的一组字符,它恰好匹配该组中的一个字符。例如:
[AEIOU]匹配任意一个大写元音字母。[0-9]匹配 0 到 9 之间的任意一个数字。[A-Za-z0-9_]匹配一个典型的单词字符(字母、数字或下划线)。
示例 1:匹配四位数年份
假设你想匹配 1900 到 2099 之间的任意年份:
^(19|20)[0-9]{2}$
^和$是锚点(我们稍后会介绍)。(19|20)匹配“19”或“20”。[0-9]{2}恰好匹配两个数字。- 合在一起,它匹配“1900”到“2099”。
示例 2:提取日志级别
给定这样一行日志:
2025-05-31 14:23:55 [Warning] Missing font file: New Times Roman.
你可以用以下模式匹配日志级别(Info、Warning、Error 等):
\[(Info|Warning|Error|Debug)\]
\[和\]匹配字面的方括号。(Info|Warning|Error|Debug)匹配这四个单词之一。
如果你预计还会有其他级别(例如 Trace 或 Fatal),可以把它们加到括号内:
\[(?:Info|Warning|Error|Debug|Trace|Fatal)\]
这里我们使用了 (?: … ) 来创建一个非捕获组(我们将在第 5 节中详细讨论分组)。
3.3 量词:重复控制
量词指定前面的元素应该匹配多少次。最常见的量词有:
*——匹配零次或多次(贪婪)。+——匹配一次或多次(贪婪)。?——匹配零次或一次(使前面的标记成为可选)。{n}——恰好匹配 n 次。{n,m}——匹配 n 到 m 次(包含两端)。{n,}——匹配 n 次或更多次。
示例 3:匹配时间戳
像 2025-05-31 14:23:48 这样的时间戳可以用以下模式匹配:
\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}
\d是[0-9]的简写。\d{4}恰好匹配四个数字(年份)。-匹配字面的连字符。\d{2}匹配两个数字(月和日)。\s+匹配一个或多个空白字符(空格、制表符)。\d{2}:\d{2}:\d{2}匹配HH:MM:SS。
如果你确定日期和时间之间恰好只有一个空格,可以用 \s 代替 \s+。
示例 4:可选的文件扩展名
假设你有 report.txt 或 report 这样的文件名。要同时匹配两者,可以使用:
^report(?:\.txt)?$
^和$确保我们匹配整个字符串。(?:\.txt)?的意思是“可选地匹配‘.txt’”。\.匹配字面的点。txt匹配“txt”。- 组后面的
?使其成为可选。
3.4 锚点:定位匹配
锚点指定的是文本中的位置,而不是实际的字符:
^——行/字符串的开头。$——行/字符串的结尾。\b——单词边界(位于\w和\W之间)。\B——非单词边界。
示例 5:以“Error”开头的行
如果你想找出所有以单词“Error”(作为独立单词)开头的行,可以这样写:
^Error\b.*$
^Error确保该行以“Error”开头。\b断言此处为单词边界,这样“Errors”或“ErrorCode”就不会被匹配。.*匹配该行的其余部分(任意字符,零次或多次)。$锚定行尾。
3.5 分组与捕获
圆括号 ( 和 ) 有两个主要用途:
- 分组:将多个标记视为一个整体(通常与量词或选择结构一起使用)。
- 捕获:存储该组匹配到的文本,以便之后取用(在代码中或替换字符串中)。
示例 6:捕获时间戳和级别
给定:
2025-05-31 14:23:55 [Warning] Missing font...
你可以这样写:
^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2})\s+\[(Info|Warning|Error|Debug)\]\s+(.*)$
(\d{4}-\d{2}-\d{2})捕获日期(Group 1)。(\d{2}:\d{2}:\d{2})捕获时间(Group 2)。(Info|Warning|Error|Debug)捕获日志级别(Group 3)。(.*)捕获消息的其余部分(Group 4)。在大多数语言中,匹配之后你可以提取 Group 1、Group 2 等:
- 例如在 C# 中:
match.Groups[1].Value给出日期。
- 例如在 C# 中:
如果你想分组但不捕获(用于选择结构或量词),请使用 (?: … )。例如:
(?:Info|Warning|Error|Debug)
不会创建带编号的反向引用。
3.6 常用元字符与转义序列
一些最常用的简写和元字符包括:
.——匹配除换行符以外的任意字符(在大多数风格中如此;有些风格允许启用“dotall”模式来匹配换行符)。\d——数字(等价于[0-9])。\D——非数字(等价于[^0-9])。\w——单词字符(字母、数字、下划线;[A-Za-z0-9_])。\W——非单词字符(任何不属于\w的字符)。\s——空白字符(空格、制表符、换行符等)。\S——非空白字符。\t、\n、\r——制表符、换行符、回车符。
提示: 在代码中编写正则表达式时,你通常需要对反斜杠进行转义。例如,在 C# 中,你可以使用逐字字符串字面量(
@"\d{4}-\d{2}-\d{2}")来避免双重转义。
3.7 实用示例:提取电子邮件地址
假设你有一份文档或配置文件,需要从中提取所有电子邮件地址。一个简单(但并不完全符合 RFC)的模式是:
\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b
\b——确保从单词边界开始。[A-Za-z0-9._%+-]+——一个或多个字母、数字、点、下划线、百分号、加号或连字符(@之前的本地部分)。@——字面的“@”符号。[A-Za-z0-9.-]+——一个或多个字母、数字、点或连字符(域名)。\.——字面的点。[A-Za-z]{2,}——顶级域名至少包含两个字母(例如“com”、“org”、“io”)。\b——在单词边界处结束,以避免部分匹配(这样就不会匹配到末尾的标点符号)。
测试一下
给定:
Please contact alice@example.com or bob.smith@my-domain.io for more info. Alternatively, reach out to admin@localhost if testing locally.
alice@example.com匹配。bob.smith@my-domain.io匹配。admin@localhost不匹配,因为localhost没有“.+ 两个或更多字母”形式的顶级域名。
3.8 实用示例:验证北美电话号码
北美编号计划(NANP)的电话号码通常是这样的:
123-456-7890(123) 456-7890123.456.7890+1 (123) 456-7890
一个实用的正则表达式可以是:
^(\+1\s?)? # Optional country code +1 and a space
(?:\(\d{3}\)|\d{3}) # Either (123) or 123
[ .-]? # Separator: space, dot, or hyphen (optional)
\d{3} # Next three digits
[ .-]? # Separator again (optional)
\d{4}$ # Last four digits
压缩之后(去掉空白和注释):
^(\+1\s?)?(?:\(\d{3}\)|\d{3})[ .-]?\d{3}[ .-]?\d{4}$
^和$将模式锚定到整个字符串。(\+1\s?)?——可选地匹配“+1”,其后可以跟一个可选的空格。(?:\(\d{3}\)|\d{3})——括号内的三个数字,或者不带括号的三个数字。[ .-]?——可选地匹配一个空格、点或连字符。\d{3}——三个数字。[ .-]?——再次匹配分隔符。\d{4}——四个数字。
这个模式可以匹配大多数常见的北美号码格式。通过调整分组和量词,你可以使其适用于其他地区,或采用更严格/更宽松的规则。
3.9 实用示例:解析日志文件
回到我们的日志示例,假设你只想提取警告消息及其时间戳。你可以使用这样的正则表达式:
^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[Warning\]\s+(.*)$
- Group 1 捕获时间戳(
2025-05-31 14:23:55)。 - Group 2 捕获消息本身(
Missing font file: New Times Roman.)。
在大多数编程语言中:
C#(使用
System.Text.RegularExpressions):var pattern = @"^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[Warning\]\s+(.*)$"; var matches = Regex.Matches(logContents, pattern, RegexOptions.Multiline); foreach (Match m in matches) { string timestamp = m.Groups[1].Value; string message = m.Groups[2].Value; Console.WriteLine($"[{timestamp}] Warning: {message}"); }Python(使用
re模块):import re pattern = r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[Warning\]\s+(.*)$' for line in log_contents.splitlines(): m = re.match(pattern, line) if m: timestamp = m.group(1) message = m.group(2) print(f"[{timestamp}] Warning: {message}")
即使你只在编辑器中单击过“Replace”(替换),这些示例也应该能让你清楚地感受到一个正则表达式有多么强大。你定义了“匹配直到 [Warning] 为止的所有内容,然后捕获其后的任何内容”,就能准确得到你想要的数据。
3.10 测试与调试正则表达式
刚接触正则表达式时,很容易忽略一些小错误——漏掉转义、放错量词的位置、忘记锚点。下面是一些建议:
使用在线测试器 regex101.com(PCRE、JavaScript、Python)、RegexStorm(适用于 .NET)或 regexr.com 等网站可以让你输入模式、粘贴测试文本,并实时查看匹配结果和高亮显示。
从简单开始,逐步扩展 如果你需要一个复杂的模式,先从一小部分开始。例如,先只用
\d{4}-\d{2}-\d{2}测试日期,然后加上时间,再加上日志级别,依此类推。为模式添加注释(在支持的情况下) 一些语言/风格支持“扩展”或“自由间距”模式,在这种模式下,你可以在正则表达式中添加空白和
# comments:(?x) # Enable free-spacing mode ^ # Start of line (\d{4}-\d{2}-\d{2}) # Group 1: Date \s+ # One or more spaces (\d{2}:\d{2}:\d{2}) # Group 2: Time \s+ # One or more spaces \[Warning\] # Literal [Warning] \s+ # One or more spaces (.*) # Group 3: Message text $这让维护和日后的修改更加容易,对于复杂的正则表达式尤其如此。
尽早转义 如果你需要匹配特殊字符(
.、*、?、+、[、]、(、)、|、^、$、\),除非你明确需要它们的特殊行为,否则一律用反斜杠对其进行转义。
4. 全面参考
下面汇总了最常用的正则表达式结构。可以把它当作一份快速参考的“速查表”。
注意: 不同的正则表达式风格(PCRE、Python、JavaScript、.NET、Java 等)在一些细节上有所不同,但下面的大部分条目都是通用的。存在特定于风格的说明时,会加以注明。
4.1 基本元字符
| 元字符 | 含义 |
|---|---|
. |
匹配除换行符以外的任意字符(除非启用了“dotall”/“singleline”模式)。 |
^ |
匹配行/字符串的开头。 |
$ |
匹配行/字符串的结尾。 |
\b |
单词边界(\w 与 \W 之间的过渡处)。 |
\B |
非单词边界(与 \b 相反)。 |
\d |
数字,等价于 [0-9]。 |
\D |
非数字,等价于 [^0-9]。 |
\w |
单词字符,等价于 [A-Za-z0-9_]。 |
\W |
非单词字符,等价于 [^A-Za-z0-9_]。 |
\s |
空白字符(空格、制表符、换行符、垂直制表符、换页符等)。 |
\S |
非空白字符(任何不被 \s 匹配的字符)。 |
\t |
制表符。 |
\n |
换行符(LF)。 |
\r |
回车符(CR)。 |
\f |
换页符。 |
\v(特定于风格) |
垂直制表符(部分风格支持)。 |
\\ |
字面的反斜杠。 |
\. |
字面的点。 |
\* |
字面的星号。 |
\+ |
字面的加号。 |
\? |
字面的问号。 |
\{ \} |
字面的花括号。 |
\[ \] |
字面的方括号。 |
\( \) |
字面的圆括号。 |
\| |
字面的竖线/选择运算符。 |
\^ |
字面的脱字符(字符串开头锚点)。 |
\$ |
字面的美元符号(字符串结尾锚点)。 |
4.2 量词
| 量词 | 含义 |
|---|---|
* |
匹配前面的标记零次或多次(贪婪)。 |
+ |
匹配前面的标记一次或多次(贪婪)。 |
? |
匹配前面的标记零次或一次(使其成为可选;也用于惰性量词)。 |
{n} |
匹配前面的标记恰好 n 次。 |
{n,} |
匹配前面的标记n 次或更多次(无上限)。 |
{n,m} |
匹配前面的标记至少 n 次但不超过 m 次。 |
*?、+?、??、{n,m}? |
上述量词的“惰性”或“非贪婪”版本(匹配尽可能少的字符)。 |
贪婪与惰性:
- 贪婪量词(
*、+、{n,m})会在仍能让模式其余部分匹配成功的前提下,尝试匹配尽可能多的文本。- 惰性量词(
*?、+?、{n,m}?)会匹配尽可能少的文本,只要整体匹配能够成功即可。
示例:贪婪与惰性
- 模式
a.*b作用于文本axxxbxxb时,会匹配axxxbxxb(贪婪:从第一个a到最后一个b)。 - 模式
a.*?b会匹配axxxb(惰性:从a到第一个b)。
4.3 字符类与字符集
| 语法 | 匹配 |
|---|---|
[abc] |
字符 a、b 或 c 中的任意一个。 |
[^abc] |
除 a、b 或 c 之外的任意字符。 |
[a-z] |
从 a 到 z 的任意小写字母。 |
[A-Z] |
从 A 到 Z 的任意大写字母。 |
[0-9] |
从 0 到 9 的任意数字。 |
[A-Za-z0-9_] |
任意“单词”字符(通常等价于 \w)。 |
[[:alnum:]](POSIX) |
字母和数字(POSIX 风格)。可在部分风格中使用(例如 GNU、支持 [[:alnum:]] 的 PCRE)。 |
[[:space:]]、[[:digit:]] 等 |
POSIX 字符类(各风格的支持情况不一)。 |
示例:匹配十六进制数字
^[0-9A-Fa-f]+$
- 匹配任何完全由十六进制数字组成的非空字符串。
4.4 选择(|)与分组
分组:
( … )- 捕获组:保存匹配到的文本,以供反向引用或提取。
- 非捕获组:
(?: … )不保存匹配内容;适用于只需分组而无需捕获的场合。
选择:
A|B匹配模式 A 或模式 B。
示例:匹配多种文件扩展名
^.*\.(?:jpg|jpeg|png|gif|bmp)$
.*\.匹配直到点(含点)为止的任意文件名部分。(?:jpg|jpeg|png|gif|bmp)匹配这五种扩展名之一。$确保扩展名位于末尾。
4.5 锚点与边界
| 锚点 | 含义 |
|---|---|
^ |
字符串开头(在多行模式下为行首)。 |
$ |
字符串结尾(在多行模式下为行尾)。 |
\A |
字符串开头(不受多行模式影响)。 |
\z 或 \Z |
字符串结尾(\z 是绝对结尾;\Z 允许末尾有一个可选的换行符)。 |
\b |
单词边界(位于 \w 和 \W 之间)。 |
\B |
非单词边界(与 \b 相反)。 |
多行模式(
m标志)
- 如果启用多行模式(通常通过一个标志,例如 .NET 中的
RegexOptions.Multiline或 Python 中的re.MULTILINE),^和$将匹配每一行的开头/结尾,而不是整个字符串的开头/结尾。
4.6 环视(零宽断言)
环视让你可以断言某个匹配之前或之后是(或不是)某些内容,而不会把这些“内容”包含在匹配结果中。
| 结构 | 含义 |
|---|---|
(?= … ) |
正向先行断言:其后的内容必须匹配 …。 |
(?! … ) |
负向先行断言:其后的内容必须不匹配 …。 |
(?<= … ) |
正向后行断言:其前的内容必须匹配 …。 |
(?<! … ) |
负向后行断言:其前的内容必须不匹配 …。 |
示例:匹配不含数字的密码
要匹配一个长度为 6–12 个字符且不包含任何数字的密码:
^(?=.{6,12}$)(?!.*\d).+$
(?=.{6,12}$)——确保长度在 6 到 12 之间。(?!.*\d)——确保任何位置都没有数字(.*\d的意思是“某处有一个数字”;(?! … )表示“字符串的其余部分不得匹配.*\d”)。.+$——最后,匹配一个或多个(任意)字符,直到结尾。
4.7 反向引用
捕获一个组之后,你可以在模式的后面部分引用它(以强制要求文本的两个部分完全相同),也可以在替换字符串中引用它。
\1、\2……引用 Group 1、Group 2 等匹配到的文本(在大多数风格中如此)。- 在替换字符串中(例如在 .NET 或 JavaScript 中),你可能会使用
$1、$2等。
示例:匹配 HTML/XML 的开始标签和结束标签
要确保开始标签 <tag> 由同名的 </tag> 闭合:
^<([A-Za-z][A-Za-z0-9]*)\b[^>]*>(.*?)</\1>$
<([A-Za-z][A-Za-z0-9]*)\b[^>]*>- 在 Group 1 中捕获标签名(例如
div、span、p)。 \b确保标签名之后是一个单词边界。[^>]*匹配开始标签中的任何属性。
- 在 Group 1 中捕获标签名(例如
(.*?)- 惰性地捕获元素内的任何内容(Group 2)。
</\1>- 匹配与 Group 1 中捕获的标签名完全相同的结束标签(例如
</div>)。
- 匹配与 Group 1 中捕获的标签名完全相同的结束标签(例如
这个简单的模式可以匹配像 <p class="intro">Hello</p> 这样的完整元素,但它无法处理同名标签的嵌套元素。对于嵌套结构,你需要更高级的解析方法(通常超出了基本正则表达式所能稳健处理的范围)。
4.8 常用简写与转义序列
| 序列 | 含义 |
|---|---|
\d |
数字(与 [0-9] 相同)。 |
\D |
非数字(与 [^0-9] 相同)。 |
\w |
单词字符 [A-Za-z0-9_]。 |
\W |
非单词字符 [^A-Za-z0-9_]。 |
\s |
空白字符 [ \t\r\n\f\v]。 |
\S |
非空白字符。 |
\t、\n、\r、\f |
制表符、换行符、回车符、换页符。 |
\0 |
空字符(U+0000)。 |
\xhh |
十六进制代码为 hh(两位十六进制数字)的字符。 |
\uhhhh |
代码为 hhhh(四位十六进制数字)的 Unicode 字符。(特定于风格:.NET、Java、带 u 标志的 JavaScript 等。) |
\cX |
控制字符(例如 \cA 为 U+0001)。 |
\Q … \E |
在某些风格(如 .NET)中,\Q 开始一个引用区段(直到 \E 为止的所有内容都按字面处理)。 |
4.9 占有量词(量词后加 +)与原子组(特定于风格)
一些正则表达式风格(如 Java、PCRE)支持占有量词(*+、++、?+、{n,m}+),它们从不回溯:
示例:
a*+ba*+匹配尽可能多的a,并且不允许通过回溯让b得以匹配。如果输入是“aaaaabc”:
a*+匹配“aaaaa”。- 然后引擎尝试匹配
b。由于下一个字符是b,整体匹配结果为“aaaaab”。
原子组 (?> … ) 有类似的效果:一旦 (?> … ) 内的子模式匹配成功,引擎就不会再回溯到其中。
注意: 并非所有风格都支持占有量词或原子组。如果你需要这些高级功能,请查阅你所用语言/运行时的文档。
4.10 标志(修饰符)与模式
大多数正则表达式引擎允许你通过标志启用或禁用某些行为。常见的标志包括:
| 标志 | 含义 | 典型语法 |
|---|---|---|
i |
不区分大小写的匹配(.NET 中为 RegexOptions.IgnoreCase)。 |
内联:(?i)pattern。 |
m |
多行模式:^ 和 $ 匹配各行的开头/结尾,而不仅是字符串的开头/结尾。 |
在 .NET 中:RegexOptions.Multiline 或 (?m)pattern。 |
s |
单行模式或 dot-all 模式:. 也匹配换行符。 |
在 .NET 中:RegexOptions.Singleline 或 (?s)pattern。 |
x |
自由间距/注释模式:忽略空白,并允许使用 # comments。 |
在 .NET 中:RegexOptions.IgnorePatternWhitespace 或 (?x)pattern。 |
U |
非贪婪模式(PCRE):互换贪婪与惰性行为(很少使用)。 | 在 PCRE 中:(?U)pattern。 |
u |
Unicode 模式(特定于风格)。 | 在 JavaScript 中使用 /u 标志,或在 .NET 中使用 RegexOptions.CultureInvariant。 |
示例:内联标志
(?im)^[a-z]+\d+\s+$
(?i)表示“不区分大小写”。(?m)表示“多行”。- 如果没有标志,
^[a-z]+只会匹配小写字母,并且只在整个字符串的开头匹配;有了i和m,它会在每一行上匹配小写或大写字母。
4.11 替换模式
当你使用正则表达式执行替换时,通常会在替换字符串中使用反向引用:
- 在许多语言中(例如 JavaScript、Python),使用
$1、$2…… - 在 .NET 中,通常同样使用
$1、$2。 - 在一些较旧的工具或风格中,可能会使用
\1、\2。
示例:把“Last, First”调换为“First Last”
原始文本:
Smith, John
Doe, Jane
正则表达式查找模式:
^([A-Za-z]+),\s+([A-Za-z]+)$
替换模式:
$2 $1
替换之后:
John Smith
Jane Doe
([A-Za-z]+)在 Group 1 中捕获姓氏。([A-Za-z]+)在 Group 2 中捕获名字。$2 $1在输出中交换两者的位置。
5. 结语
到现在,你应该已经扎实地掌握了:
- 正则表达式为什么对文本处理极为宝贵。
- 如何编写基本模式:字面字符、字符类、量词、锚点。
- 如何使用分组、环视和反向引用进行更高级的匹配。
- 哪些标记和结构构成了大多数实用正则表达式的“核心”。
- 需要温习时去哪里查找(上面的参考部分,或 regexstorm.net/reference 这样的网站)。
只需几十分钟的练习——针对示例文本编写正则表达式——你就能迅速建立信心。请记住:
- 尽早测试、经常测试:在在线测试器或 IDE 的“Find in Files”(在文件中查找)中测试——实时看到匹配结果,远比手动肉眼检查代码更有启发。
- 可读性很重要。 如果模式变得难以阅读,请使用自由间距模式(
(?x))并添加注释。 - 对于常见任务,保持简单。你很少需要最晦涩的语法;匹配日志级别、日期、电子邮件地址或基本的 CSV 字段,通常用简单直接的字符类和量词就能完成。
掌握基础之后,你可以探索更高级的主题,例如:
- 原子分组和占有量词(用于对性能要求很高的模式)。
- 递归模式(在 PCRE 或一些高级风格中),用于匹配嵌套结构。
- 条件子模式(在 PCRE 或 .NET 中),在决定模式的下一部分之前,先测试某个组是否已经匹配。
不过,这些主题就留待以后再谈。现在,请借助上面的示例和参考表,在你的下一个文本处理挑战中尝试运用正则表达式。用不了多久,你就会发现自己开始用模式来思考——而且再也回不去了。
恭喜你踏上了打造模式的旅程!把这份指南放在手边,收藏你最喜欢的正则表达式测试器,并记住:一个写得好的正则表达式,往往能把数小时的手动解析变成一行优雅的代码。