Regex 入门必读:核心概念详解
文本数据无处不在,从简单的日志文件到复杂的网页结构,从用户输入的验证到海量信息的提取。在处理这些文本时,我们常常需要查找、替换或验证符合特定“模式”的字符串。手动编写代码来处理这些模式既繁琐又容易出错。有没有一种更强大、更灵活的方式来描述这些模式呢?
这就是 正则表达式(Regular Expression) 的用武之地。Regex,简写为 Regexp 或 RE,是一种用于描述字符串匹配模式的强大工具。它不是一种独立的编程语言,而是一种在许多编程语言(如 Python, Java, JavaScript, C#, PHP)、文本编辑器(如 VS Code, Sublime Text, Vim)、以及各种命令行工具(如 grep, sed, awk)中都能找到并使用的“迷你语言”。
掌握 Regex,就像是给你的文本处理能力装上了涡轮增压器。它能让你用简洁的语法实现复杂的文本操作,极大地提高工作效率。
但是,对于初学者来说,Regex 的语法看起来可能有些古怪和令人望而生畏,充满了各种符号和斜杠。不过别担心,就像学习任何新语言一样,Regex 也有其核心的“词汇”和“语法”。一旦你理解了这些核心概念,Regex 的世界就会为你打开大门。
本文的目标就是详细讲解 Regex 的核心概念,帮助你跨越入门的障碍。我们将一步步解析 Regex 的基本组成部分,并通过丰富的例子来加深理解。
为什么你需要学习 Regex?
在深入概念之前,我们先快速回顾一下 Regex 的常见应用场景,看看它能解决哪些实际问题:
- 数据验证 (Validation): 检查用户输入是否符合特定格式,例如邮箱地址、电话号码、身份证号、URL、密码强度等。
- 文本搜索与查找 (Searching): 在大量文本中快速找到符合特定模式的所有字符串,比如查找所有日期、所有链接、所有包含特定关键词的句子等。
- 文本替换 (Replacement): 将符合特定模式的字符串替换成其他内容,例如标准化日期格式、去除HTML标签、敏感信息脱敏等。
- 文本分割 (Splitting): 根据复杂的模式将字符串分割成多个部分。
- 代码解析 (Parsing): 虽然不是完整的解析器,但 Regex 可以用于提取代码中的特定元素,如函数名、变量、注释等(尽管对于复杂语法,专用的解析器更健壮)。
- 日志分析 (Log Analysis): 从混乱的日志文件中提取关键信息,如错误类型、IP地址、时间戳等。
简而言之,任何涉及基于模式匹配的文本处理任务,Regex 都能大显身手。
Regex 的核心组成部分:模式 (Pattern)
Regex 的本质就是一个模式 (Pattern)。这个模式由一系列字符和特殊符号组成,用来描述你想要匹配的字符串的特征。一个模式可以非常简单,比如只包含普通字符,也可以非常复杂,包含各种元字符、量词、分组等。
理解 Regex,就是理解构成这个模式的各种元素及其含义。
1. 字面量字符 (Literal Characters)
Regex 中最基本的元素就是字面量字符。这些字符就是它们本身的字面意义。例如,模式 cat 将精确匹配字符串中的 cat 这三个连续的字符。
- 模式:
cat - 匹配的字符串:
"The **cat** sat on the mat."-> 匹配到 “cat” - 不匹配的字符串:
"A dog barked."-> 没有匹配到
大多数字母、数字和标点符号(除非它们是 Regex 的特殊符号)都是字面量字符。
2. 元字符 (Metacharacters)
Regex 的强大之处在于其元字符 (Metacharacters)。这些字符不像字面量字符那样代表本身,而是具有特殊的含义或功能。它们是构建复杂模式的基石。理解元字符是学习 Regex 的关键第一步。
以下是一些最常见的元字符:
-
.(点号): 匹配任意单个字符(除了换行符)
点号是一个非常有用的通配符。它可以匹配除换行符(\n)以外的任何单个字符。- 模式:
c.t - 匹配:
"**cat**","**cot**","**cut**","**c@t**" - 不匹配:
"ct"(缺少中间字符),"ca\nt"(.通常不匹配换行)
这就像是问号填空,
c_t,中间的空可以是任何字符。 - 模式:
-
^(脱字符): 匹配字符串的开始
当^放在模式的开头时,它表示匹配必须发生在目标字符串的起始位置。- 模式:
^Hello - 匹配:
"**Hello** world!" - 不匹配:
"Good morning, Hello!"(Hello 不在开头)
- 模式:
-
$(美元符号): 匹配字符串的结束
当$放在模式的末尾时,它表示匹配必须发生在目标字符串的结束位置。- 模式:
world!$ - 匹配:
"Hello **world!**" - 不匹配:
"Hello world! How are you?"(world! 不在结尾)
- 模式:
-
\(反斜杠): 转义字符
如果一个元字符是你想要匹配的字面量字符,你就需要使用反斜杠\来“转义”它,取消它的特殊含义。- 如果你想匹配字面量点号
.而不是任意字符,模式应该是\.。 - 如果你想匹配字面量反斜杠
\本身,模式应该是\\。
例如:
- 模式:
www\.example\.com(匹配网址中的点号) - 匹配:
"Visit us at **www.example.com**" - 不匹配:
"www-example-com"
需要转义的常见元字符包括:
.,^,$,*,+,?,(,),[,],{,},|,\。 - 如果你想匹配字面量点号
3. 字符集 (Character Sets 或 Character Classes)
光有通配符 . 还不够,我们常常需要匹配特定集合中的任何一个字符。这时就用到字符集,用方括号 [] 表示。
-
[abc]: 匹配字符a、b或c中的任意一个。- 模式:
[aeiou](匹配任何一个英文元音字母) - 匹配:
"The **a**pple is r**e**d."-> 匹配 ‘a’, ‘e’ - 不匹配:
"Sky"(没有元音)
- 模式:
-
[a-z]: 使用连字符-可以指定一个字符范围。[a-z]匹配从a到z的任意一个小写字母。- 模式:
[0-9]匹配任意一个数字 (0-9)。 - 模式:
[A-Z]匹配任意一个大写字母 (A-Z)。 - 模式:
[a-zA-Z]匹配任意一个大写或小写字母。 - 模式:
[a-zA-Z0-9]匹配任意一个字母或数字。
例如:
- 模式:
gr[ae]y - 匹配:
"The color is **gray**.","The color is **grey**." - 不匹配:
"The color is green."
- 模式:
-
[^...]: 在方括号内,如果^是第一个字符,它表示排除。[^...]匹配不在指定集合中的任意单个字符。- 模式:
[^0-9]匹配任何一个非数字字符。 - 模式:
[^aeiou]匹配任何一个非元音字符。
例如:
- 模式:
a[^bc]d - 匹配:
"aZd","a1d","a d"(中间不是 b 或 c) - 不匹配:
"abd","acd"
- 模式:
-
常用的字符集简写 (Shorthand Character Classes)
Regex 提供了一些非常方便的简写来表示常见的字符集:\d: 等价于[0-9],匹配任意一个数字。\D: 等价于[^0-9],匹配任意一个非数字字符。\w: 等价于[a-zA-Z0-9_],匹配任意一个词汇字符(字母、数字或下划线)。在某些Regex引擎中,\w也可能包含其他语言的字母。\W: 等价于[^a-zA-Z0-9_],匹配任意一个非词汇字符。\s: 匹配任意一个空白字符(空格、制表符\t、换行符\n、回车符\r、换页符\f等)。\S: 匹配任意一个非空白字符。
这些简写极大地简化了模式的书写。例如,要匹配一个由数字组成的字符串,你不再需要写
[0-9][0-9][0-9]...,而可以使用\d结合后面要讲的量词。例如:
- 模式:
\d{3}(后面会解释{3}) 匹配三个连续的数字。 - 模式:
\w+匹配一个或多个词汇字符(通常用于匹配单词)。
4. 量词 (Quantifiers)
到目前为止,我们学习的模式都只能匹配固定数量的字符(比如 cat 匹配三个字符,\d 匹配一个数字)。但实际应用中,我们经常需要匹配某个模式重复若干次的情况。量词 (Quantifiers) 就是用来指定一个元素(可以是单个字符、字符集、或分组)应该出现多少次。
量词放在它们所作用的元素后面。
以下是常见的量词:
-
*(星号): 匹配前一个元素零次或多次
这是最常用的量词之一。它表示前面的元素可以完全不出现,也可以出现任意次。- 模式:
a*b - 匹配:
"b"(a 出现零次) - 匹配:
"ab"(a 出现一次) -
匹配:
"aaab"(a 出现多次) -
模式:
go*gle(匹配 google, gogle, gooogle 等) - 匹配:
"I use **google** every day." - 匹配:
"Is it **gogle** or google?"
- 模式:
-
+(加号): 匹配前一个元素一次或多次
类似于*,但要求前面的元素至少出现一次。- 模式:
a+b - 匹配:
"ab"(a 出现一次) - 匹配:
"aaab"(a 出现多次) -
不匹配:
"b"(a 未出现) -
模式:
\d+(匹配一个或多个数字) - 匹配:
"My number is **12345**."-> 匹配 “12345” - 匹配:
"Amount: **99**."-> 匹配 “99” - 不匹配:
"No digits here."
- 模式:
-
?(问号): 匹配前一个元素零次或一次
这表示前面的元素是可选的。- 模式:
colou?r(匹配 color 或 colour) -
匹配:
"The **color** is red.","The **colour** is blue." -
模式:
\d{3}-?\d{3}-?\d{4}(匹配美国电话号码格式 123-456-7890 或 1234567890) - 匹配:
"Call **123-456-7890**","Call **1234567890**"
- 模式:
-
{n}: 匹配前一个元素恰好 n 次
花括号{}可以指定精确的重复次数。- 模式:
\d{4}(匹配恰好四位数字) - 匹配:
"Year: **2023**" - 不匹配:
"Year: 23"
- 模式:
-
{n,}: 匹配前一个元素至少 n 次
指定一个最小值,最大次数不限。- 模式:
\d{3,}(匹配至少三位数字) - 匹配:
"ID: **100**","Account: **123456**" - 不匹配:
"Pin: 12"
- 模式:
-
{n,m}: 匹配前一个元素至少 n 次,但不超过 m 次
指定一个重复次数的范围。- 模式:
[a-zA-Z]{5,10}(匹配长度在 5 到 10 之间的字母串) - 匹配:
"Word: **hello**","Phrase: **wonderful**" - 不匹配:
"Short: hi","Verylongword: extraordinary"
- 模式:
-
量词的贪婪性 (Greedy) 与惰性 (Lazy)
默认情况下,Regex 的量词是贪婪的 (Greedy)。这意味着它们会尽可能多地匹配字符,直到整个模式无法匹配为止。例如,模式
<.*>在字符串"<b>bold</b><i>italic</i>"中匹配什么?
贪婪匹配会找到<**bold</b><i>italic**>,因为它尽可能多地匹配.。有时候我们不希望这种行为,而是希望量词尽可能少地匹配,一旦满足模式就停止。这时可以在量词后面加上
?,使其变为惰性 (Lazy) 或非贪婪 (Non-Greedy)。*?: 匹配前一个元素零次或多次,但尽可能少。+?: 匹配前一个元素一次或多次,但尽可能少。??: 匹配前一个元素零次或一次,但尽可能少。{n,}?: 匹配前一个元素至少 n 次,但尽可能少。{n,m}?: 匹配前一个元素 n 到 m 次,但尽可能少。
例如,模式
<.*?>在字符串"<b>bold</b><i>italic</i>"中匹配什么?
惰性匹配会找到<**b**>和<**i**>,因为*?在匹配到第一个>时就停止了。理解贪婪和惰性对于处理包含重复标记的文本(如HTML/XML)非常重要。
5. 分组 (Grouping) 与捕获 (Capturing)
圆括号 () 在 Regex 中有两个主要作用:分组和捕获。
-
分组 (Grouping): 将多个元素视为一个整体来应用量词或进行其他操作。
如果你想匹配连续出现的ab两次,直接写ab{2}是错误的,这只会匹配abb。你需要将ab组合成一个整体:(ab){2}。- 模式:
(ab)+ -
匹配:
"ab","abab","ababab" -
模式:
(可爱){3} - 匹配:
"她说了三次**可爱可爱可爱**"
- 模式:
-
捕获 (Capturing): 圆括号内的部分被称为一个捕获组 (Capturing Group)。当模式匹配成功时,每个捕获组匹配到的实际文本会被“捕获”并存储起来,供后续使用(比如在替换操作中引用,或在编程语言中提取匹配的部分)。
捕获组从左到右,根据左括号出现的顺序编号,第一个左括号对应的组是组 1,第二个是组 2,依此类推。整个模式的匹配结果是组 0。
例如:
- 模式:
(\d{4})-(\d{2})-(\d{2})(匹配 YYYY-MM-DD 格式的日期) - 字符串:
"今天是 2023-10-27" - 匹配结果: “2023-10-27” (组 0)
- 捕获组 1: “2023”
- 捕获组 2: “10”
- 捕获组 3: “27”
在许多编程语言中,你可以轻松访问这些捕获组的内容。在一些文本编辑器的替换功能中,你可以使用
\1,\2等来引用捕获组的内容。例如,将 YYYY-MM-DD 格式替换为 MM/DD/YYYY 格式:
* 查找模式:(\d{4})-(\d{2})-(\d{2})
* 替换为:\2/\3/\1(引用第二个、第三个和第一个捕获组)
* 结果: “今天是 10/27/2023” - 模式:
-
非捕获组 (Non-Capturing Groups): 如果你只需要分组的功能,而不需要捕获匹配的内容,可以使用
(?:...)创建一个非捕获组。这可以提高性能,并避免创建不必要的捕获组。- 模式:
(?:ab)+(分组并应用量词,但不捕获 ab)
虽然对于初学者来说,了解捕获组更重要,但知道非捕获组的存在也很有益。
- 模式:
6. 或运算 (Alternation)
竖线符号 | 用作或运算 (Alternation),表示匹配左边或右边的模式。
- 模式:
cat|dog(匹配 “cat” 或 “dog”) - 匹配:
"I like **cat**s and **dog**s."-> 匹配 “cat” 和 “dog”
或运算可以与分组 () 结合使用,来限制或运算的作用范围。
- 模式:
(cat|dog)s(匹配 “cats” 或 “dogs”) - 匹配:
"I like **cats** and **dogs**." - 不匹配:
"I like catdog."(没有匹配 cats 或 dogs)
如果不用分组,模式 cat|dogs 将匹配 “cat” 或 “dogs”。
7. 边界匹配 (Anchors)
前面我们已经介绍了 ^ 和 $ 这两个常用的边界匹配符,它们分别匹配字符串的开始和结束。除了字符串的边界,Regex 还提供了其他类型的边界匹配。
-
\b(单词边界): 匹配一个单词的边界。单词边界定义为词汇字符(\w)和非词汇字符(\W)之间的位置,或者词汇字符与字符串的开始/结束之间的位置。
\b不匹配任何字符,它匹配的是一个位置。- 模式:
cat(会匹配 catalog 中的 cat) -
模式:
\bcat\b(只匹配独立的单词 cat) -
字符串:
"The cat sat on the catalog." - 模式
cat: 匹配 “cat” (第一个), “cat” (catalog 中) - 模式
\bcat\b: 只匹配第一个独立的 “cat”
\b对于精确匹配完整的单词非常有用。 - 模式:
-
\B(非单词边界): 匹配不是单词边界的位置。这通常用于查找作为更大单词一部分的模式。- 模式:
\Bcat\B(匹配 cat,但 cat 前后都不是单词边界) - 字符串:
"The **cat** sat on the **catalog**." - 模式
\Bcat\B: 不匹配任何东西 (cat 前后是单词边界或字符串开头/结尾)。 - 模式
cata\Blog: 不匹配 (cata 后面是非单词边界,但 log 后面是单词边界). - 模式
pro\bgram: 不匹配 (\b 在 o 和 g 之间,o 是词汇,g 是词汇,之间不是边界). - 模式
pro\Bgram: 匹配 “program” 中的 “pro” 和 “gram” 之间的位置。 (这个例子比较难理解,重点掌握\b即可)。
主要记住
^(字符串开始),$(字符串结束), 和\b(单词边界) 这三个最常用的边界匹配符。 - 模式:
8. 反向引用 (Backreferences)
前面提到,捕获组会记住它们匹配到的文本。反向引用 (Backreferences) 允许你在同一个 Regex 模式中引用之前捕获组匹配到的文本。它们用 \1, \2, \3 等表示,分别对应第一个、第二个、第三个捕获组。
-
模式:
(.)\1(.):捕获任意一个字符到组 1。\1: 匹配与组 1 完全相同的字符。-
这个模式匹配任意一个重复两次的字符。
-
字符串:
"Look at this **ee**xample."-> 匹配 “ee” - 字符串:
"Mississippi"-> 匹配 “ss”, “ss”, “pp”
-
模式:
(\w+)\s+\1(匹配一个单词,后面跟着一个或多个空格,然后再跟着同一个单词)(\w+):捕获一个或多个词汇字符到组 1。\s+: 匹配一个或多个空白字符。-
\1: 匹配与组 1 完全相同的文本。 -
字符串:
"This is a **test test**."-> 匹配 “test test” - 字符串:
"Is this this correct?"-> 匹配 “this this”
反向引用在查找重复的词语或结构时非常有用。
9. 前后查找 (Lookarounds) – 了解概念
虽然不是最核心的入门概念,但了解前后查找 (Lookarounds) 的存在能让你知道 Regex 的能力边界在哪里。前后查找是一种零宽度断言(Zero-width Assertions),就像 \b, ^, $ 一样,它们匹配的是一个位置,而不是实际的字符。它们用于在不消耗字符的情况下,检查某个位置的前面或后面是否满足特定的模式。
-
肯定前瞻 (Positive Lookahead):
(?=...)匹配当前位置,当且仅当后面紧跟着的文本匹配括号内的模式。- 模式:
Windows(?=XP|Vista|7)匹配 “Windows”,但只在后面跟着 “XP”, “Vista”, 或 “7” 的时候。它只匹配 “Windows” 这五个字符,后面的版本号不包含在匹配结果中。
- 模式:
-
否定前瞻 (Negative Lookahead):
(?!...)匹配当前位置,当且仅当后面紧跟着的文本不匹配括号内的模式。- 模式:
Windows(?!XP|Vista|7)匹配 “Windows”,但只在后面不跟着 “XP”, “Vista”, 或 “7” 的时候。
- 模式:
-
肯定后顾 (Positive Lookbehind):
(?<=...)匹配当前位置,当且仅当前面紧挨着的文本匹配括号内的模式。- 模式:
(?<=$) \d+匹配一个或多个数字,但只在前面紧跟着 “$” 符号的时候。它只匹配数字部分。
- 模式:
-
否定后顾 (Negative Lookbehind):
(?<!...)匹配当前位置,当且仅当前面紧挨着的文本不匹配括号内的模式。- 模式:
(?<!$) \d+匹配一个或多个数字,但只在前面不跟着 “$” 符号的时候。
- 模式:
前后查找非常强大,可以用于一些复杂的匹配场景,例如匹配价格时只匹配数字但不包含货币符号,或者匹配某个词语但排除特定上下文中的情况。对于初学者,可以先理解并掌握前面的核心概念,在需要时再深入学习前后查找。
核心概念总结表
为了方便回顾,我们将上面讲解的核心概念整理成表格:
| 类型 | 符号/语法 | 描述 | 例子 | 说明 |
|---|---|---|---|---|
| 字面量 | 任意普通字符 | 匹配字符本身 | a, 1, ? |
大部分字母、数字、标点符号 |
| 元字符 | . |
匹配任意单个字符 (通常不包括换行) | c.t |
通配符 |
^ |
匹配字符串的开始位置 | ^Hello |
行首/串首锚定 | |
$ |
匹配字符串的结束位置 | world!$ |
行尾/串尾锚定 | |
\ |
转义字符,使元字符匹配其字面意义 | \., \\ |
匹配特殊符号本身 | |
| 字符集 | [...] |
匹配方括号中任意一个字符 | [aeiou] |
定义一组可能的字符 |
[a-z] |
匹配指定范围内的任意一个字符 | [0-9], [A-Za-z] |
定义字符范围 | |
[^...] |
匹配不在方括号中的任意一个字符 | [^0-9] |
排除指定集合的字符 | |
| 字符集简写 | \d |
匹配数字 ([0-9]) |
\d{3} |
数字简写 |
\D |
匹配非数字 ([^0-9]) |
\D+ |
非数字简写 | |
\w |
匹配词汇字符 ([a-zA-Z0-9_]) |
\w+ |
字母、数字、下划线简写 | |
\W |
匹配非词汇字符 ([^a-zA-Z0-9_]) |
\W |
非字母、数字、下划线简写 | |
\s |
匹配空白字符 (\t, \n, \r, \f, ) |
\s+ |
空格、Tab 等简写 | |
\S |
匹配非空白字符 ([^\s]) |
\S+ |
非空白字符简写 | |
| 量词 | * |
匹配前一个元素零次或多次 (贪婪) | a* |
{0,} |
+ |
匹配前一个元素一次或多次 (贪婪) | a+ |
{1,} |
|
? |
匹配前一个元素零次或一次 (贪婪) | a? |
{0,1} |
|
{n} |
匹配前一个元素恰好 n 次 | \d{4} |
精确次数 | |
{n,} |
匹配前一个元素至少 n 次 | \d{3,} |
最小次数限制 | |
{n,m} |
匹配前一个元素 n 到 m 次 | \d{3,5} |
次数范围限制 | |
*?, +?, ??, {n,}?, {n,m}? |
使对应的量词变为惰性 (非贪婪) | <.*?> |
尽可能少地匹配 | |
| 分组/捕获 | (...) |
分组,并捕获匹配的文本到组中 | (ab)+ |
将 ab 视为一个整体并应用量词 |
(?:...) |
非捕获分组 | (?:ab)+ |
仅分组,不捕获 | |
| 或运算 | | |
匹配左边或右边的模式 | cat|dog |
逻辑 OR |
| 边界 | \b |
匹配单词边界 | \bcat\b |
匹配独立单词 |
\B |
匹配非单词边界 | 匹配非单词边界位置 | ||
| 反向引用 | \n (n为数字) |
匹配第 n 个捕获组匹配到的文本 | (.)\1 |
引用之前捕获的内容 |
| 前后查找 | (?=...) |
肯定前瞻 (零宽度) | \d+(?=%) |
后面跟着 % 的数字 |
(?!...) |
否定前瞻 (零宽度) | \d+(?!%) |
后面没跟着 % 的数字 | |
(?<=...) |
肯定后顾 (零宽度) | (?<=$)\d+ |
前面跟着 $ 的数字 | |
(?<!...) |
否定后顾 (零宽度) | (?<!$)\d+ |
前面没跟着 $ 的数字 |
实践是最好的老师
理论知识是基础,但 Regex 的真正掌握来自于实践。学习 Regex 最好的方法是:
- 从小处着手: 从简单的字面量和基本元字符开始尝试。
- 利用在线工具: 有许多优秀的在线 Regex 测试器 (如 regex101.com, regextester.com, regexr.com)。这些工具通常提供实时的匹配结果、详细的解释以及备忘录,对于学习和调试非常有帮助。将你的模式和测试字符串粘贴进去,看看它是如何工作的。
- 拆解复杂模式: 遇到复杂的模式时,不要试图一次性理解全部。把它拆分成小的部分,逐个分析每个字符、字符集、量词和分组的作用。
- 大量练习: 尝试解决各种不同的文本匹配问题,从简单的邮箱验证到复杂的日志解析。
- 查阅文档: 不同的 Regex 引擎(比如 Perl-compatible Regular Expressions PCRE, Java Regex, Python re 模块等)在细节上可能略有差异。当你开始在特定环境中使用 Regex 时,查阅该环境的文档是必不可少的。
将核心概念结合起来的例子
让我们通过几个例子来展示如何组合这些核心概念:
例 1: 匹配一个简单的邮箱地址格式 (非严格)
模式: ^\w+@\w+\.\w+$
^: 匹配字符串开始。\w+: 匹配一个或多个词汇字符(邮箱用户名部分)。@: 匹配字面量@符号。\w+: 匹配一个或多个词汇字符(域名部分)。\.: 匹配字面量点号(域名和顶级域名之间的点)。\w+: 匹配一个或多个词汇字符(顶级域名部分,如 com, org)。$: 匹配字符串结束。
这个模式可以匹配 “[email protected]” 或 “[email protected]”,但无法匹配 “[email protected]” 或 “test@localhost”。这是一个非常基础的邮箱验证模式,实际的邮箱验证 Regex 会复杂得多,需要考虑更多字符和格式。
例 2: 查找 HTML 标签对及其内容 (简单示例,慎用于复杂 HTML)
模式: <(\w+)>.*?</\1>
<: 匹配字面量<。(\w+): 捕获一个或多个词汇字符(标签名,如 body, p, div 等)到组 1。>: 匹配字面量>。.*?: 匹配任意字符零次或多次,惰性匹配。这是关键,确保它只匹配到第一个闭合标签之前的内容。</: 匹配字面量</。\1: 反向引用组 1 匹配到的标签名。这确保了闭合标签的名称与开始标签相同。>: 匹配字面量>。
字符串: "<body><p>Hello</p></body>"
* 模式 <(\w+)>.*?</\1> 会匹配 "<body><p>Hello</p></body>" (外层 body 标签,因为 .*? 惰性匹配,第一次遇到 </ 就尝试匹配 </\1>,发现 </p> 不行,于是继续匹配直到遇到 </body>)。
* 更精确地匹配内层 <p>Hello</p>,需要更精细的模式,比如 <p>.*?</p>。或者结合多次匹配。
这个例子说明了分组、量词(特别是惰性量词)和反向引用的组合使用。同时也要注意 Regex 的局限性,对于嵌套结构的复杂文本(如完整的 HTML/XML),使用专门的解析库通常比 Regex 更健壮和可靠。
例 3: 提取电话号码中的区号和本地号码
假设电话号码格式是 (XXX) XXX-XXXX 或 XXX-XXX-XXXX。
模式: \(?(\d{3})\)?[- ]?(\d{3})[- ]?(\d{4})
\(?: 匹配可选的字面量((需要转义)。(\d{3}): 捕获三个数字(区号)到组 1。\)?: 匹配可选的字面量)(需要转义)。[- ]?: 匹配可选的连字符-或空格。(\d{3}): 捕获接下来的三个数字到组 2。[- ]?: 匹配可选的连字符-或空格。(\d{4}): 捕获最后四个数字到组 3。
字符串: "Call (123) 456-7890 or 987-654-3210"
* 匹配 "**(123) 456-7890**"
* 组 1: “123”
* 组 2: “456”
* 组 3: “7890”
* 匹配 "**987-654-3210**"
* 组 1: “987”
* 组 2: “654”
* 组 3: “3210”
这个例子展示了可选元素 (?)、字符集 ([- ]) 和分组的组合使用,以便捕获特定部分的数据。
总结与展望
Regex 是一项非常有价值的技能,尤其对于任何需要处理文本数据的IT从业者来说。本文详细介绍了 Regex 的核心概念,包括:
- 字面量字符: 匹配自身。
- 元字符:
.,^,$,\, 等特殊符号。 - 字符集 (
[]) 及简写 (\d,\w,\s): 匹配特定集合或类型的字符。 - 量词 (
*,+,?,{}): 控制匹配元素的重复次数,以及贪婪/惰性行为。 - 分组 (
()) 与捕获: 将模式部分组合,并提取匹配内容。 - 或运算 (
|): 匹配多个可能模式之一。 - 边界匹配 (
^,$,\b): 匹配特定位置而非字符。 - 反向引用 (
\n): 在模式中引用之前捕获的内容。 - 前后查找 (
(?=),(?!,(?<=,(?<!): 零宽度断言,检查上下文。
掌握这些核心概念是迈入 Regex 世界的关键一步。Regex 的语法在不同语言和工具中基本通用,一旦学会,可以在很多地方复用你的知识。
学习 Regex 需要时间和练习。不要害怕那些看起来复杂的模式,分解它们,理解每个部分的含义,并利用在线工具进行实验。随着经验的积累,你会发现自己能够越来越快地构建和理解复杂的正则表达式,从而更高效地解决文本处理的难题。
继续探索,不断练习,你将成为一名 Regex 的文本处理高手!