一文学会正则匹配:正则表达式基础指南 – wiki基地


一文学会正则匹配:正则表达式基础指南

正则表达式(Regular Expression,简称 Regex 或 Regexp)是处理字符串的强大工具,它用一种简洁的方式描述了字符串的模式(pattern)。无论你是程序员、数据分析师,还是需要批量处理文本的任何人,掌握正则表达式都能极大地提升你的工作效率。

初次接触正则表达式,你可能会觉得它的语法像一串神秘的符号咒语,让人望而生畏。但实际上,正则表达式就像学习一门新的语言,它有自己的字母、单词、语法规则。一旦你掌握了这些基本构建块,你就能像搭积木一样构建出强大的文本匹配模式。

本文旨在提供一份详尽的正则表达式基础指南,带你从零开始,一步步理解并掌握正则表达式的核心概念和常用语法。读完本文,你应该能够独立编写简单的正则表达式,并理解更复杂的模式。

为什么学习正则表达式?

在处理文本数据时,你经常会遇到以下需求:

  1. 查找特定模式的文本: 找出所有电子邮件地址、电话号码、日期、URL 等。
  2. 验证输入格式: 检查用户输入的密码是否符合要求、手机号格式是否正确、邮箱地址是否有效。
  3. 替换或删除文本: 批量删除文档中的空白行、替换特定的字符串格式(例如将 YYYY-MM-DD 替换为 MM/DD/YYYY)。
  4. 从文本中提取信息: 从网页内容中抓取所有链接、从日志文件中提取错误信息。
  5. 分割字符串: 根据复杂的规则将字符串分割成多个部分。

手动处理这些任务既耗时又容易出错,而正则表达式正是为解决这些问题而生的。它可以让你用一行代码完成原本需要多行甚至几十行代码才能完成的任务。

Regex 的基本概念:模式匹配

正则表达式的核心概念是“模式匹配”。你定义一个模式,然后用这个模式去匹配一个目标字符串。如果字符串中的某个部分符合这个模式,那么匹配成功。

正则表达式的引擎

需要注意的是,正则表达式的实现(称为“引擎”)在不同的编程语言和工具有细微的差别。例如,Perl、Python、Java、JavaScript、grep 等都有自己的正则表达式引擎,它们在支持的特性或语法细节上可能略有不同。本文主要介绍主流的、通用的正则表达式语法,这在大多数现代编程语言和工具中都是适用的。建议在学习过程中结合在线正则表达式测试工具(如 regex101.com, regexr.com)进行实践,这些工具通常可以选择不同的引擎,并提供详细的匹配解释。

构建块:基础语法

正则表达式由普通字符(literal characters)和特殊字符(metacharacters)组成。普通字符就是它们本身,特殊字符则有特殊的含义。

1. 普通字符 (Literal Characters)

大多数字符在正则表达式中都代表它们自身。
例如:
* 模式 cat 会匹配字符串 “The cat sat on the mat.” 中的 “cat”。
* 模式 123 会匹配字符串 “The number is 123.” 中的 “123”。
* 模式 Hello World 会匹配字符串 “Hello World!” 中的 “Hello World”。

2. 特殊字符 (Metacharacters)

这些字符具有特殊的含义,是构建复杂模式的关键。

2.1 点号 .:匹配除换行符外的任意单个字符

点号是最常用的特殊字符之一。它匹配除了换行符(\n)之外的任意单个字符。

  • 模式 a.c 会匹配 “abc”, “adc”, “axc”, “a3c”, “a c” 等,但不会匹配 “ac” 或 “abbc”。
  • 模式 . 会匹配字符串中的每一个非换行符字符。

2.2 插入符 ^:匹配行的开始

插入符 ^ 匹配目标字符串的开始位置。如果使用多行模式(某些工具或语言支持),它也可以匹配每一行的开始。

  • 模式 ^The 只会匹配以 “The” 开头的行或字符串。
    • ^The 匹配 “The quick brown fox…”
    • ^The 不匹配 “…quick brown The fox…”
  • 模式 ^$: 匹配空行(行首紧接着行尾)。

2.3 美元符号 $:匹配行的结束

美元符号 $ 匹配目标字符串的结束位置。如果使用多行模式,它也可以匹配每一行的结束。

  • 模式 end$ 只会匹配以 “end” 结尾的行或字符串。
    • document end$ 匹配 “The document end
    • document ending 不匹配 “The document ending
  • 模式 ^$:如前所述,匹配空行。

2.4 反斜杠 \:转义特殊字符

如果想匹配一个特殊字符本身,而不是它的特殊含义,就需要使用反斜杠 \ 进行转义。

  • 如果想匹配一个实际的点号 ., 需要使用模式 \.
    • 模式 www\.example\.com 会匹配 “www.example.com”。
    • 模式 www.example.com (没有转义) 会匹配 “wwwXexampleYcom” 等(XY是任意字符)。
  • 要匹配反斜杠本身,需要使用 \\
  • 其他需要转义的常见特殊字符包括:., ^, $, *, +, ?, {, }, (, ), |, [, ], \.

2.5 竖线 |:逻辑或 (OR)

竖线 | 用于在两个或多个模式之间进行选择,匹配其中任意一个。

  • 模式 cat|dog 会匹配字符串中的 “cat” 或 “dog”。
  • 模式 (a|b)c 会匹配 “ac” 或 “bc”。

3. 字符集 (Character Sets)

字符集使用方括号 [] 定义,用于匹配方括号内列出的任意单个字符。

3.1 基本字符集 []

[abc]:匹配字符 ‘a’, ‘b’, 或 ‘c’ 中的任意一个。
* 模式 gr[ae]y 会匹配 “gray” 或 “grey”。

[0123456789]:匹配任意一个数字。这等同于使用 [0-9] (见下文范围)。
[aeiou]:匹配任意一个元音字母。

3.2 范围 [ - ]

在字符集中,可以使用连字符 - 来指定一个字符范围。
[a-z]:匹配任意一个小写字母。
[A-Z]:匹配任意一个大写字母。
[a-zA-Z]:匹配任意一个英文字母(大小写不敏感)。
[0-9]:匹配任意一个数字。
[a-zA-Z0-9]:匹配任意一个字母或数字。

  • 模式 [0-9]{3} (稍后会介绍 {3} 量词) 可以匹配连续的三个数字,如 “123”, “007”。
  • 模式 [A-Za-z]+ (稍后会介绍 + 量词) 可以匹配一个或多个字母组成的单词,如 “Hello”, “world”, “Regex”。

3.3 否定字符集 [^]

在方括号内的开头使用插入符 ^ 可以创建一个否定字符集,匹配 不在 该集合内的任意单个字符。

[^abc]:匹配除了 ‘a’, ‘b’, 或 ‘c’ 之外的任意单个字符(不包括换行符)。
[^0-9]:匹配任意一个非数字字符(不包括换行符)。
[^\n]:匹配除了换行符之外的任意单个字符(这与 . 大多数情况下的行为相同,但更明确)。

  • 模式 [^aeiou] 会匹配任意一个非元音字母的字符。
  • 模式 p[^in]t 会匹配 “pat”, “pet”, “pzt” 等,但不会匹配 “pit” 或 “pnt”。

4. 量词 (Quantifiers)

量词用于指定一个字符、一个字符集或一个分组应该出现多少次。

4.1 星号 *:匹配零次或多次

* 匹配其前面的元素出现零次或多次。

  • 模式 a* 会匹配 “”, “a”, “aa”, “aaa”, …。
  • 模式 ab*c 会匹配 “ac”, “abc”, “abbc”, “abbbc”, …。
  • 模式 [0-9]* 会匹配任意长度的数字串,包括空字符串。
  • 模式 .* 会匹配除换行符外的任意数量的字符(包括零个)。这通常用于匹配一行中的任意内容。

4.2 加号 +:匹配一次或多次

+ 匹配其前面的元素出现一次或多次。

  • 模式 a+ 会匹配 “a”, “aa”, “aaa”, …,但不匹配 “”。
  • 模式 ab+c 会匹配 “abc”, “abbc”, “abbbc”, …,但不匹配 “ac”。
  • 模式 [0-9]+ 会匹配一个或多个数字,如 “1”, “123”, “98765”。
  • 模式 .+ 会匹配除换行符外的任意数量的字符(至少一个)。

4.3 问号 ?:匹配零次或一次

? 匹配其前面的元素出现零次或一次。它使得前面的元素成为可选的。

  • 模式 a? 会匹配 “” 或 “a”。
  • 模式 colou?r 会匹配 “color” 或 “colour”。
  • 模式 Nov(ember)? 会匹配 “Nov” 或 “November”。

4.4 花括号 {}:精确或范围匹配

花括号 {} 提供了更精确的量词控制。

  • {n}:匹配前面的元素恰好出现 n 次。
    • 模式 \d{3} 会匹配连续的三个数字,如 “123”, “050”。
    • 模式 [a-z]{5} 会匹配连续的五个小写字母,如 “world”。
  • {n,}:匹配前面的元素至少出现 n 次。
    • 模式 \d{3,} 会匹配三个或更多个数字,如 “123”, “12345”。
    • 模式 [A-Z]{2,} 会匹配两个或更多个大写字母。
  • {n,m}:匹配前面的元素出现 n 到 m 次(包含 n 和 m)。
    • 模式 \d{3,5} 会匹配三到五个数字,如 “123”, “1234”, “12345”。
    • 模式 [a-z]{1,3} 会匹配一到三个小写字母,如 “a”, “ab”, “abc”。

量词的默认行为:贪婪匹配 (Greedy)

默认情况下,量词 *, +, {n,}, {n,m} 是“贪婪的”。这意味着它们会尝试匹配尽可能多的字符,同时仍然允许整个模式匹配成功。

  • 考虑字符串 <p><b>Hello</b> <i>World</i></p>
  • 模式 <.*>:因为 . 匹配任意字符,* 尽可能多地匹配,它会从第一个 < 开始,一直匹配到 最后一个 >。所以它会匹配整个字符串 <p><b>Hello</b> <i>World</i></p>

4.5 非贪婪/惰性匹配 (Lazy)

在量词后面加上 ? 可以使其变为非贪婪或惰性。它会匹配尽可能少的字符。

  • 在量词 *, +, ?, {n}, {n,}, {n,m} 后面加上 ?

    • *?:零次或多次,但尽可能少。
    • +?:一次或多次,但尽可能少。
    • ??:零次或一次,但尽可能少(总是匹配零次,除非必须匹配一次)。
    • {n}?, {n,}?, {n,m}?:匹配指定次数范围,但尽可能少。
  • 回到字符串 <p><b>Hello</b> <i>World</i></p>

  • 模式 <.*?>:这个模式是非贪婪的。它会从第一个 < 开始,匹配任意字符(.),但因为是非贪婪 (*?),它会在遇到 第一个 > 时停止。
    • 它会匹配 <p>
    • 然后继续查找下一个匹配,找到 <b>
    • 然后 </b>
    • 然后 <i>
    • 然后 </i>
    • 然后 </p>

在需要匹配标签、引号内内容等场景下,非贪婪匹配非常有用。

5. 分组 (Grouping)

圆括号 () 用于将正则表达式的一部分分组。分组有几个重要作用:

  1. 应用量词到整个组:

    • 模式 (ab)+ 会匹配 “ab”, “abab”, “ababab”, …。如果没有括号 ab+ 则只匹配 “abb”, “abbb”, …。
    • 模式 (hey){2} 会匹配 “heyhey”。
  2. 捕获匹配的文本 (Capturing Groups): 默认情况下,分组会“捕获”它们匹配到的文本。这使得你可以在后续处理中引用这些被捕获的部分(例如在替换操作中)。捕获的文本通常可以通过编号(如 \1, \2)或名称进行引用。

    • 考虑字符串 “FirstName LastName”
    • 模式 (\w+) (\w+)
      • (\w+) 匹配一个或多个单词字符并捕获为第一组 (\1),匹配 “FirstName”。
      • 空格匹配空格。
      • (\w+) 匹配一个或多个单词字符并捕获为第二组 (\2),匹配 “LastName”。
    • 在许多工具或语言中,可以使用 \2 \1 来将 “FirstName LastName” 替换为 “LastName FirstName”。
  3. 逻辑分组: 用于控制 | 操作符的作用范围。

    • 模式 (cat|dog)food 会匹配 “catfood” 或 “dogfood”。
    • 模式 cat|dogfood 会匹配 “cat” 或 “dogfood”。

5.1 非捕获分组 (?:...)

如果你只需要使用括号进行分组(例如应用量词或控制 | 范围),但不需要捕获匹配的内容,可以使用非捕获分组 (?:...)。这在性能上可能稍有优势,并且不会创建额外的捕获组编号。

  • 模式 (?:ab)+ 仍然会匹配 “ab”, “abab” 等,但匹配到的子串不会被捕获。
  • 模式 gr(?:ay|ey) 匹配 “gray” 或 “grey”,且不捕获 “ay” 或 “ey”。

6. 常用字符类简写 (Shorthand Character Classes)

为了方便,正则表达式提供了一些常用字符集的简写形式:

  • \d:匹配任意一个数字,等同于 [0-9]
    • \d+ 匹配一个或多个数字。
  • \D:匹配任意一个非数字字符,等同于 [^0-9]
  • \w:匹配任意一个“单词”字符(字母、数字、下划线),等同于 [a-zA-Z0-9_]
    • \w+ 匹配一个或多个单词字符,常用于匹配单词。
  • \W:匹配任意一个非“单词”字符,等同于 [^a-zA-Z0-9_]
  • \s:匹配任意一个空白字符(空格、制表符 \t、换行符 \n、回车符 \r、换页符 \f 等)。
    • \s+ 匹配一个或多个连续的空白字符。
  • \S:匹配任意一个非空白字符,等同于 [^\s]

使用这些简写可以使正则表达式更简洁易读。

  • 模式 \d{3}-\d{3}-\d{4} 可以匹配美国格式的电话号码 “123-456-7890″。

7. 边界匹配 (Anchors)

除了 ^$ 匹配行的开始和结束,还有 \b\B 用于匹配单词边界。

  • \b:匹配一个单词边界。单词边界是指一个单词字符(\w)和一个非单词字符(\W)之间的位置,或者是一个单词字符和字符串的开始/结束之间的位置。简单来说,它标记了一个单词的开始或结束。
    • 模式 \bcat\b 会匹配独立单词 “cat”,如 “The cat sat.” 或 “cat, dog”。
    • 模式 \bcat\b 不会匹配 “catalogue”, “concatenate”, “tomcat” 中的 “cat”,因为那不是一个完整的单词。
  • \B:匹配一个非单词边界。它匹配 \b 不匹配的位置。
    • 模式 \Bcat\B 会匹配 “concatenate” 中的 “cat”。
    • 模式 cat\B 会匹配 “catalogue” 中的 “cat”。
    • 模式 \Bcat 会匹配 “tomcat” 中的 “cat”。

8. 回溯引用 (Backreferences)

在正则表达式中,可以使用 () 创建捕获组。然后可以使用 \1, \2, \3 等回溯引用来引用前面对应的捕获组匹配到的文本。\1 引用第一个捕获组,\2 引用第二个,以此类推。

回溯引用在查找重复模式或进行替换时非常有用。

  • 模式 (\w+)\s+\1
    • (\w+) 匹配一个或多个单词字符并捕获为第一组。
    • \s+ 匹配一个或多个空白字符。
    • \1 引用第一个捕获组匹配的内容。
    • 这个模式会匹配连续重复的单词,如 “hello hello”, “world world”。
  • 在很多替换操作中,可以使用回溯引用来重组文本。例如,将 YYYY-MM-DD 格式的日期转换为 MM/DD/YYYY
    • 原始字符串: “Date: 2023-10-26”
    • 模式: (\d{4})-(\d{2})-(\d{2})
      • (\d{4}) 捕获年份 (2023) 作为第一组 (\1)。
      • - 匹配连字符。
      • (\d{2}) 捕获月份 (10) 作为第二组 (\2)。
      • - 匹配连字符。
      • (\d{2}) 捕获日期 (26) 作为第三组 (\3)。
    • 替换字符串: \2/\3/\1
    • 结果: “Date: 10/26/2023”

9. 零宽度断言 (Lookarounds)

零宽度断言是一种特殊的构造,它匹配一个位置,但不消耗字符串中的字符。它们用于指定匹配必须满足的条件,而这个条件本身不包含在最终的匹配结果中。

  • (?=...):肯定先行断言 (Positive Lookahead)。匹配当前位置后面紧跟着 ... 模式的位置。
    • 模式 \d{3}(?=-) 会匹配后面紧跟着连字符 - 的三个数字。在字符串 “123-456” 中,它会匹配 “123”,但不包括 -
  • (?!...):否定先行断言 (Negative Lookahead)。匹配当前位置后面 没有 紧跟着 ... 模式的位置。
    • 模式 \d{3}(?!-?) 会匹配后面 不是 紧跟着可选连字符 - 的三个数字。例如,在 “123 456-789” 中,它会匹配 “123”,但不匹配 “456”。
  • (?<=...):肯定后行断言 (Positive Lookbehind)。匹配当前位置前面紧跟着 ... 模式的位置。
    • 模式 (?<=\$)\d+ 会匹配前面紧跟着美元符号 $ 的一串数字。在字符串 “Price: $19.99” 中,它会匹配 “19”,但不包括 $
  • (?<!...):否定后行断言 (Negative Lookbehind)。匹配当前位置前面 没有 紧跟着 ... 模式的位置。
    • 模式 (?<!\$)\d+ 会匹配前面 没有 紧跟着美元符号 $ 的一串数字。

零宽度断言通常用于在不捕获边界字符的情况下匹配核心内容。例如,从 HTML 标签中提取文本,但不包括标签本身:(?<=>).*?(?=<\/) (这是一个简化的例子)。

10. 标志 (Flags)

正则表达式的匹配行为可以通过设置标志 (flags) 来修改。这些标志通常在正则表达式模式的末尾指定(如 /pattern/flags),或者作为函数参数传递。常见的标志包括:

  • i (Case-insensitive):忽略大小写。
    • 模式 /cat/i 会匹配 “cat”, “Cat”, “CAT” 等。
  • g (Global):全局匹配。查找所有匹配项,而不是在找到第一个匹配后停止。
  • m (Multiline):多行模式。使 ^$ 匹配每一行的开始和结束,而不仅仅是整个字符串的开始和结束。
  • s (Dotall / Singleline):单行模式。使点号 . 匹配包括换行符在内的任意字符。

具体支持的标志取决于你使用的正则表达式引擎和工具。

11. 构建更复杂的模式:组合使用

掌握了以上基本构建块后,就可以开始组合它们来构建更复杂的模式了。关键在于分析你要匹配的目标字符串的结构,然后用正则表达式的语法来描述这个结构。

示例:匹配一个简单的邮箱地址格式

我们来尝试匹配一个简化的邮箱地址格式,例如 [email protected]
一个邮箱地址通常包含:
1. 用户名前缀:由字母、数字、下划线、点等组成(需要更精确的定义)。
2. @ 符号。
3. 域名:由字母、数字、连字符组成,后跟一个点。
4. 顶级域名 (TLD):由字母组成。

一个简化的模式可以是:[\w.]+@[\w.-]+\.[a-zA-Z]{2,}

分解这个模式:
* [\w.]+:匹配用户名。[\w.] 匹配单词字符 (\w) 或点号 (.),+ 表示一个或多个这样的字符。允许了点号在用户名中出现,例如 first.last
* @:匹配字面字符 @
* [\w.-]+:匹配域名部分。[\w.-] 匹配单词字符、点号或连字符,+ 表示一个或多个。允许了子域名和带连字符的域名,例如 sub.domain-name
* \.:匹配字面字符点号 .,用于分隔域名和顶级域名。
* [a-zA-Z]{2,}:匹配顶级域名。[a-zA-Z] 匹配任意字母,{2,} 表示至少两个字母。

注意: 这是一个非常简化的邮箱地址匹配模式。真实的邮箱地址格式非常复杂,要编写一个完全符合 RFC 标准的正则表达式几乎不可能且不实用。上面的例子只是用来演示如何组合基本元素。

示例:匹配 HTML 标签(非嵌套)

匹配像 <p>, <h1>, <div> 这样的标签:
模式:<[a-zA-Z]+>

分解:
* <:匹配字面字符 <
* [a-zA-Z]+:匹配一个或多个英文字母(标签名)。
* >:匹配字面字符 >

如果标签可能包含属性,例如 <a href="...">
模式:<[a-zA-Z]+.*?>

分解:
* <[a-zA-Z]+:匹配标签的开始和标签名。
* .*?:非贪婪地匹配标签名后的任意字符(属性等),直到遇到下一个 >
* >:匹配标签的结束。

这个模式仍然非常简化,不能处理嵌套标签等复杂情况,但展示了如何处理标签内容。

12. 在不同语言中使用正则表达式

正则表达式本身是一种独立的模式语言,但在实际应用中,你需要通过编程语言提供的正则表达式库来使用它。不同的语言有不同的库和函数,但核心的模式语法是相似的。

  • Python: 使用 re 模块。常用的函数有 re.search(), re.match(), re.findall(), re.sub(), re.compile().
  • JavaScript: 正则表达式是语言内置的。可以使用 /pattern/flags 字面量或 new RegExp("pattern", "flags") 构造函数。字符串对象有 match(), search(), replace(), split() 方法,RegExp 对象有 test(), exec() 方法。
  • Java: 使用 java.util.regex 包中的 PatternMatcher 类。
  • 其他语言: 大多数现代编程语言都提供了成熟的正则表达式支持。

了解你所使用语言的正则表达式库的具体用法和函数签名是非常重要的。

13. 学习和实践的建议

  1. 从基础开始: 逐个理解每个元字符、字符集和量词的含义。不要试图一次掌握所有东西。
  2. 多实践: 正则表达式的学习离不开实践。找一些文本数据,尝试编写正则表达式来匹配、查找或替换。
  3. 使用在线工具: 利用 regex101.com 或 regexr.com 等在线工具。它们提供实时的匹配结果、解释,并且可以测试不同引擎的行为,极大地帮助理解和调试。
  4. 分步构建: 对于复杂的模式,不要试图一次写完。先写一个匹配核心部分的模式,然后逐步添加限制条件(如边界、前后的断言)。
  5. 查阅文档: 当遇到不确定的语法或想了解特定语言的正则特性时,查阅官方文档或在线教程。
  6. 理解贪婪与非贪婪: 这是初学者容易混淆的地方,花时间理解它们的区别和应用场景。
  7. 小步快跑: 每次添加一小部分模式就测试一下,确保它按预期工作。

14. 常见的正则表达式应用场景回顾

  • 数据验证: 邮箱、电话、邮政编码、身份证号、IP地址、URL 格式验证。
  • 文本解析和提取: 从日志文件提取特定条目、从配置文件提取参数、从网页提取链接或内容。
  • 数据清洗: 去除多余的空白字符、格式化数据、统一日期格式。
  • 搜索和替换: 在大量文件中查找特定模式并替换、在代码编辑器中进行高级查找替换。
  • 语法高亮和代码分析: 正则表达式是很多编辑器和工具实现语法高亮的基础。

总结

正则表达式是一项非常强大的技能,能够让你以高效的方式处理文本数据。虽然它的语法最初可能看起来很密集,但通过理解少数核心概念——普通字符、特殊字符(元字符)、字符集、量词、分组、边界和断言——并结合大量的实践,你就能逐步掌握它。

从简单的匹配开始,逐步构建更复杂的模式。利用在线工具的帮助,不要害怕尝试和犯错。随着你解决的问题越多,你对正则表达式的理解和运用能力也会越强。

“一文学会”可能有点夸张,但通过本文的详细介绍,你已经掌握了正则表达式最核心和常用的基础知识。剩下的就是通过持续的实践,将这些知识内化为解决实际问题的能力。

现在,是时候打开你的代码编辑器或在线正则表达式测试工具,开始你的正则表达式探索之旅了!祝你学习顺利,享受用简洁模式解决复杂文本问题的乐趣!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部