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 的文本处理高手!