Regex 入门必读:核心概念详解 – wiki基地


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 的常见应用场景,看看它能解决哪些实际问题:

  1. 数据验证 (Validation): 检查用户输入是否符合特定格式,例如邮箱地址、电话号码、身份证号、URL、密码强度等。
  2. 文本搜索与查找 (Searching): 在大量文本中快速找到符合特定模式的所有字符串,比如查找所有日期、所有链接、所有包含特定关键词的句子等。
  3. 文本替换 (Replacement): 将符合特定模式的字符串替换成其他内容,例如标准化日期格式、去除HTML标签、敏感信息脱敏等。
  4. 文本分割 (Splitting): 根据复杂的模式将字符串分割成多个部分。
  5. 代码解析 (Parsing): 虽然不是完整的解析器,但 Regex 可以用于提取代码中的特定元素,如函数名、变量、注释等(尽管对于复杂语法,专用的解析器更健壮)。
  6. 日志分析 (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]: 匹配字符 abc 中的任意一个。

    • 模式: [aeiou] (匹配任何一个英文元音字母)
    • 匹配: "The **a**pple is r**e**d." -> 匹配 ‘a’, ‘e’
    • 不匹配: "Sky" (没有元音)
  • [a-z]: 使用连字符 - 可以指定一个字符范围。[a-z] 匹配从 az 的任意一个小写字母。

    • 模式: [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 最好的方法是:

  1. 从小处着手: 从简单的字面量和基本元字符开始尝试。
  2. 利用在线工具: 有许多优秀的在线 Regex 测试器 (如 regex101.com, regextester.com, regexr.com)。这些工具通常提供实时的匹配结果、详细的解释以及备忘录,对于学习和调试非常有帮助。将你的模式和测试字符串粘贴进去,看看它是如何工作的。
  3. 拆解复杂模式: 遇到复杂的模式时,不要试图一次性理解全部。把它拆分成小的部分,逐个分析每个字符、字符集、量词和分组的作用。
  4. 大量练习: 尝试解决各种不同的文本匹配问题,从简单的邮箱验证到复杂的日志解析。
  5. 查阅文档: 不同的 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-XXXXXXX-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 的文本处理高手!

发表评论

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

滚动至顶部