正则表达式【详细解读】

目录

正则表达式是什么?

基本概念

主要用途

正则表达式的元字符

核心的元字符及其功能:

重复限定符:

重复限定符案例

位置匹配:

位置匹配案例

字符集与否定:

字符集与否定案例

特殊字符:

特殊字符案例

分组与引用:

分组与引用案例

前瞻与后顾:

前瞻与后顾案例

简写字符集

 零宽度断言(前后预查)

前瞻断言(Lookahead Assertions)

后顾断言(Lookbehind Assertions)

应用场景

注意事项

正则表达式的标志

 (忽略大小写)

g (全局匹配)

m (多行模式)

x (扩展模式)

s (单行模式)

u (Unicode模式)

U (非贪婪量词)

l (本地化匹配)

A (从开始匹配)

Z (从结束匹配)

E (结束匹配)

正则表达式的贪婪匹配与惰性匹配

贪婪匹配(Greedy Matching):

惰性匹配(Lazy Matching):


正则表达式是什么?

正则表达式,简称Regex,是一种强大的文本处理语言,用于描述字符串的模式。它能够帮助用户在文本中进行复杂的搜索、匹配、替换和提取操作。正则表达式的基本理念是用有限的符号来定义和匹配无限的字符串序列。

基本概念

  • 正则表达式 是一个字符串,其中包含普通字符(如字母和数字)和特殊字符(元字符),用于定义一个搜索模式。
  • 它广泛应用于各种编程语言、文本编辑器和命令行工具中,如JavaScript、Python、grep等。

主要用途

  • 搜索:在大量文本中查找符合特定模式的字符串。
  • 验证:检查字符串是否符合特定格式,如邮箱地址、电话号码的格式验证。
  • 替换:根据模式替换文本,例如批量修改文档中的某些部分。
  • 提取:从文本中提取符合模式的信息,如从网页中抓取链接。

正则表达式的元字符

正则表达式元字符是一组具有特殊意义的字符,它们在正则表达式中不表示自己字面上的字符,而是用来控制匹配规则的逻辑和行为。

核心的元字符及其功能:

  1. 重复限定符:

    • *: 匹配前面的子表达式零次或多次。
    • +: 匹配前面的子表达式一次或多次。
    • ?: 匹配前面的子表达式零次或一次。
    • {m}: 精确匹配m次。
    • {m,n}: 匹配前面的子表达式至少m次,但不超过n次。

重复限定符案例

  • * 示例:\d* 可以匹配任何数字组成的字符串,包括空字符串。例如,“123”,“”,“000”。

  • + 示例:[a-z]+ 会匹配至少一个小写字母,如“hello”,但不匹配空字符串。

  • ? 示例:colou?r 匹配“color”或“colour”,表示“u”可以出现也可以不出现。

  • {m} 示例:\d{3} 仅匹配三个数字,如“123”,但不匹配“12”或“1234”。

  • {m,n} 示例:[A-Z]{2,4} 匹配2到4个大写字母,如“AB”,“ABC”,“ABCD”,但不匹配单个字母或超过四个字母的字符串。

  1. 位置匹配:

    • ^: 在模式开始处匹配,如果在方括号内,则表示取反。
    • $: 在模式结束处匹配。
    • \b: 匹配单词边界。
    • \A: 匹配字符串的开始(非所有引擎都支持)。
    • \Z: 匹配字符串的结束,或在行尾前的结束(非所有引擎都支持)。

位置匹配案例

  • ^ 示例:^Hello 在多行模式下,匹配每行开头为“Hello”的行。

  • $ 示例:world$ 在多行模式下,匹配每行结尾为“world”的行。

  • \b 示例:\bthe\b 只匹配独立的单词“the”,不匹配“there”中的“the”。

  • \A 示例:\AStart 确保整个字符串从“Start”开始。

  • \Z 示例:在多行模式下,End\Z 确保字符串或每行的结束是“End”。

  1. 字符集与否定:

    • [abc]: 匹配任何一个在方括号内的字符。
    • [^abc]: 匹配任何不在方括号内的字符。
    • [0-9]: 等同于\d,匹配数字。
    • [a-zA-Z]: 匹配任何字母。

字符集与否定案例

  • [abc] 示例:[abc]at 匹配“cat”,“bat”,“cat”。

  • [^abc] 示例:[^abc]at 匹配除了“a”、“b”、“c”之外的任何字符紧跟“at”,如“dat”。

  • [0-9] 示例:等同于\d,[0-9]{2} 匹配任意两位数字,如“12”。

  • [a-zA-Z] 示例:匹配任何单个字母,如“a”到“z”或“A”到“Z”。

  1. 特殊字符:

    • .: 匹配除换行符之外的任意单个字符。
    • \: 用于转义特殊字符,使其失去特殊含义,如\.匹配点号本身。
    • |: 用于选择,表示“或”,匹配左边或右边的表达式之一。

特殊字符案例

  • . 示例:. 匹配除换行符外的任意字符,.com 会匹配任何以“.com”结尾的字符串,如“example.com”。

  • \ 示例:\. 匹配点号本身,而不是任何字符,如\.\d匹配点后跟着数字,如“1.2”。

  • | 示例:red|blue 匹配“red”或“blue”。

  1. 分组与引用:

    • ( ): 分组,将括号内的表达式作为整体处理,也可以用于后向引用。
    • \1, \2, ...: 引用前面分组的内容,用于重复匹配。

分组与引用案例

  • ( ) 示例:(abc)\1 匹配“abcabc”,其中\1引用第一个括号内的内容。

  • \1, \2, ... 示例:(.\d+)(.\d+)\2\1 匹配两个数字序列并确保它们按相同的顺序重复,如“12321”。

  1. 前瞻与后顾:

    • (?=...): 正向前瞻,确保后面跟的是...但不消耗字符。
    • (?!...): 负向前瞻,确保后面不跟的是...。
    • (?<=...): 正向后顾,确保前面是...但不消耗字符。
    • (?<!...): 负向后顾,确保前面不是...。

前瞻与后顾案例

  • (?=...) 示例:word(?=ing) 匹配“word”仅当其后紧跟“ing”,但不包括“ing”。

  • (?!...) 示例:word(?!ing) 匹配“word”但不匹配其后紧跟“ing”的情况。

  • (?<=...) 示例:(?<=\d)th 匹配任何数字后的“th”,确保“th”前有数字,但不包括数字。

  • (?<!...) 示例:(?<!\d)th 匹配“th”,但确保它前面不是数字。

简写字符集

正则表达式中的简写字符集提供了一种高效且简洁的方式来匹配特定类别的字符,这些简写字符在处理文本时非常实用。下面是正则表达式中简写字符集的详细介绍:

  1. . (点号) - 除换行符外的所有字符。这个元字符广泛用于匹配任何单个字符,但需要注意的是,在不同的上下文中或通过特定标志(如m多行模式),它可能被配置为匹配包括换行符在内的所有字符。

  2. \w (Word) - 匹配所有字母数字字符以及下划线。这等同于 [a-zA-Z0-9_]。它用于识别所谓的“单词字符”,在大多数语言中,这涵盖了字母和数字。

  3. \W (Not Word) - 与\w相反,匹配所有非字母数字字符,包括符号和空格。这等同于 [^a-zA-Z0-9_]([^\w]),用于排除单词字符。

  4. \d (Digit) - 等同于 [0-9],用于匹配任何数字字符。这是数字字符的快捷方式。

  5. \D (Not Digit) - 与\d相反,匹配任何非数字字符。这可以表示为 [^0-9]([^\d]),用于排除数字。

  6. \s (Space) - 匹配任何空白字符,包括空格、制表符(\t)、换行符(\n)、回车符(\r)、垂直制表符(\v)以及Unicode中的其他空白字符 [\t\n\f\r\p{Z}]。这是处理文本间隔时的常用选项。

  7. \S (Not Space) - 与\s相反,匹配任何非空白字符。这用于寻找文本中的连续非空白序列,等同于: [^\s]。

  8. \f (Form Feed) - 匹配一个换页符,这是一种较少见的页面分隔符。

  9. \n (Newline) - 匹配一个换行符,这是文本文件中常见的行结束符。

  10. \r (Carriage Return) - 匹配一个回车符,常见于Windows系统中的行结束符。

  11. \t (Tab) - 匹配一个制表符,用于文本缩进或对齐。

  12. \v (Vertical Tab) - 匹配一个垂直制表符,虽然在现代文本中不常用。

  13. \p 匹配 CR/LF(等同于 \r\n),用来匹配 DOS 行终止符

  14. \b (单词边界): 这个元字符用于匹配一个位置,该位置位于一个单词字符(\w,即字母、数字或下划线)和一个非单词字符(\W,即除字母、数字、下划线之外的任何字符)之间,或者字符串的开始或结束,如果它们分别紧跟着或紧跟着一个单词字符。简单来说,\b帮助我们找到单词的边缘。"word".replace(/\b/g, '-'):这会在单词的开始和结束处插入破折号,结果为-word-,因为\b匹配到了单词的边界。

  15. \B (非单词边界): 相对于\b,\B匹配的是没有单词边界的位置,即匹配两个单词字符之间或两个非单词字符之间的位置,确保其左右两边都是单词字符或都不是单词字符。换句话说,\B用于定位在单词内部的位置,不处于单词与非单词字符的边界上。"a1b2c".replace(/\B/g, '-'):这会在每个单词字符之间插入破折号,结果为a-1-b-2-c,因为\B匹配到的不是单词边界,即每个数字和字母之间的位置。

 零宽度断言(前后预查)

零宽度断言(Zero-Width Assertions)是正则表达式中一种特殊的技术,它们用于匹配特定的位置,而不消耗或匹配任何实际的字符。这意味着它们不会成为匹配结果的一部分,但可以帮助控制匹配的上下文条件。零宽度断言分为两大类:前瞻断言(Lookahead Assertions)和后顾断言(Lookbehind Assertions)。

符号 描述
?= 正先行断言-存在
?! 负先行断言-排除
?<= 正后发断言-存在
?<! 负后发断言-排除

前瞻断言(Lookahead Assertions)

前瞻断言允许你指定一个位置,该位置之后必须满足某个条件,但不包括这个条件本身在匹配结果中。

  • 正向肯定预查 (?=...):确保紧跟当前匹配点之后的模式存在,但不匹配它。

    • 示例:\w+(?=\s*=\s*"[^"]*") 会匹配URL参数前的关键词,如在keyword="value"中匹配keyword,但不包括等号及引号内的内容。
  • 正向否定预查 (?!...):确保紧跟当前匹配点之后的模式不存在。

    • 示例:^.*(?!\.)$ 会匹配不以点号结尾的行。

后顾断言(Lookbehind Assertions)

后顾断言检查匹配点之前的文本是否满足某个条件,同样不消耗字符。

  • 负向后顾预查 (?<!...):确保匹配点之前不出现某个模式。

    • 示例:\b(?<!\d)\d+\b 匹配独立的数字,但不匹配数字串中的数字,如在“123abc456”中匹配“123”和“456”。
  • 正向后顾预查 (?<=...):确保匹配点之前确实出现了某个模式。

    • 示例:(?<=\d)\D 会匹配任何非数字字符,但仅当其前是一个数字时。

应用场景

  • 密码强度验证:如题目所述,可以使用零宽断言来确保密码包含至少两种字符类型,如大小写字母、数字或特殊字符。
  • 文本分割:精确分割字符串,比如在HTML标签间提取文本,而不包括标签本身。
  • 数据验证:在不改变原始字符串的情况下验证格式,如邮箱地址的验证,确保@符号前后的内容符合规则。
  • 避免重复匹配:在复杂的模式中,通过预查避免匹配到不应该匹配的字符序列。

注意事项

  • JavaScript支持限制:早期JavaScript引擎不支持后顾断言,但现代引擎如V8已支持。
  • 性能考量:复杂的零宽度断言可能会增加正则表达式的执行时间,尤其是在处理大量文本时。
  • 可读性:虽然强大,但过度使用或复杂的断言会降低正则表达式的可读性,建议适当注释。

正则表达式的标志

  1.  (忽略大小写)

    • 说明: 这个标志使得正则表达式在匹配时对大小写不敏感。
    • 示例: /hello/i 会匹配 "Hello", "HELLO", "hello"。例如,在JavaScript中:
      const regex = /hello/i; console.log(regex.test("Hello")); // 输出: true
  2. g (全局匹配)

    • 说明: 强制正则表达式进行全局匹配,即查找所有匹配项,而不仅仅第一个。
    • 示例: /abc/g 在字符串 "abc def abc" 中会找到两次 "abc"。例如:
      const text = "abc def abc"; const regex = /abc/g; let matches = text.match(regex); 
      console.log(matches); // 输出: ["abc", "abc"]
  3. m (多行模式)

    • 说明: 改变 ^ 和 $ 的行为,使其分别匹配每一行的开始和结束。
    • 示例: /^start/m 在多行文本中每行的开始查找 "start"。例如,处理多行文本时:
      const multiLineText = "start of line 1\nline 2\nstart of line 3"; 
      const regex = /^start/mg; let matches = multiLineText.match(regex); 
      console.log(matches); // 输出: ["start", "start"]
  4. x (扩展模式)

    • 说明: 允许在正则表达式中插入空白字符以提高可读性,且不影响匹配结果。
    • 示例: /hello\ world/x 在这种模式下,空白被忽略,但通常用于内部注释,具体实现依赖于语言。
  5. s (单行模式)

    • 说明: 让.特殊字符能够匹配包括换行符在内的所有字符,使得整个文本被视为一行。
    • 示例: /text.*end/s 在包含多行文本中,从"text"开始直到"end"的所有内容,包括中间的换行符。
  6. u (Unicode模式)

    • 说明: 在JavaScript中启用Unicode属性转义,适用于处理Unicode字符。
    • 示例: /\p{L}/u 匹配任何Unicode字母字符。
  7. U (非贪婪量词)

    • 说明: 改变量词(*, +, ?, {n}, {n,}, {n,m})的行为,尽可能少地匹配。
    • 示例: /a.*?b/U 在字符串 "ababcd" 中,.*? 会尽可能少地匹配,直到遇到第一个 "b"。
  8. l (本地化匹配)

    • 说明: 这个标志的使用较少见,且依赖于具体实现,它可能影响字符分类,以适应不同语言的字符规则。
    • 示例(概念性): 假设在理论上支持l标志的环境中,我们想匹配德语中的单词,考虑到德语中的特殊字符如"ü"、"ä"、"ö"等。然而,由于实际环境中很少直接提供l标志,通常会通过设置语言环境来间接实现类似功能,而非直接在正则表达式中使用。
// 假设有一个函数setLocaleForRegex('de')来设置德语环境
setLocaleForRegex('de');
regex = /\bwort\b/l; // 假设'l'在这里表示匹配德语单词
  1. A (从开始匹配)

    • 说明: 类似于 ^,但在多行模式下,确保匹配的是除了第一行之外的每行的开始。
    • 示例(概念性):
import re

text = "First line\nSecond line start\nThird line"
pattern = re.compile(r'Astart', re.MULTILINE)
matches = pattern.findall(text)
print(matches)  # 实际上,Python的re模块不直接支持A标志,这里仅作概念说明

正确的做法是使用正则表达式的特性模拟,虽然Python不直接支持A标志,但可以使用逻辑组合达到类似效果: 

pattern = re.compile(r'^(?!^).+start', re.MULTILINE)
  1. Z (从结束匹配)

    • 说明: 类似于 $,但在多行模式下,确保匹配的是除了最后一行之外的每行的结束,不包括换行符。
    • 示例(概念性):
# 假设Python的re模块
text = "First line\nSecond line end\nThird line"
# 实际上应使用正则表达式的技巧而非直接的Z标志
pattern = re.compile(r'end(?!\n$)', re.MULTILINE)
matches = pattern.findall(text)
print(matches)  # 模拟匹配除最后一行外以"end"结束的行
  1. E (结束匹配)

    • 说明: 主要用于Perl兼容正则表达式中的扩展模式控制,不是所有环境都支持。
    • 示例(概念性):
my $text = "Sample text";
my $regex = qr/...(?#这是一个注释).../e; # 注意:这里的'e'标志与'E'不同,用于执行代码,而非'E'标志

正则表达式的贪婪匹配与惰性匹配

贪婪匹配(Greedy Matching):

贪婪匹配是正则表达式默认的行为模式,它尽可能多地匹配字符。当正则表达式中的量词(如*、+、?、{n,}等)遇到可匹配的字符时,会尽可能地扩展匹配范围,直到无法继续匹配为止。如果后续的模式无法匹配,正则引擎会逐步回溯,撤销之前的部分匹配,直到找到一个合适的匹配。

示例: 假设我们有字符串 "hello world",并使用正则表达式 /lo+/ 来匹配 "lo" 后面跟着尽可能多的字符。

  • 贪婪匹配: 正则表达式会匹配到 "llo world",因为+是贪婪的,它尽可能多地匹配 "l" 后面的字符,直到不能匹配为止。

惰性匹配(Lazy Matching):

惰性匹配,也称为非贪婪匹配或懒惰求值,与贪婪匹配相反,它尽可能少地匹配字符。在量词后面加上问号 ? 可以使量词变为惰性。这意味着它会匹配尽可能少的字符,仅匹配刚好满足整个正则表达式要求的字符数量。

示例: 使用相同的字符串 "hello world",但这次我们使用惰性匹配的正则表达式 /lo+?/。

  • 惰性匹配: 正则表达式将只匹配到 "lo",因为+?会尽可能少地匹配字符,仅匹配到第一个满足条件的实例。

更具体的例子:

假设我们要从HTML文本中提取链接,文本片段如下:

<a href="https://example.com">Example</a> <a href="http://test.com">Test</a>
  • 贪婪匹配例子: 使用正则表达式 /href="(.*?)"/g(这里不完全正确,仅用于说明贪婪匹配)可能会匹配到整个从第一个 href 到最后一个引号的文本,即 "https://example.com">Example</a> <a href="http://test.com",因为它试图匹配尽可能多的字符直到下一个 "。

  • 惰性匹配例子: 使用 /href="(.*?)"/g(这里正确地使用了惰性匹配),每次匹配时只会匹配到最短的链接,即 "https://example.com" 和 "http://test.com",因为惰性匹配在遇到第一个可能的结束条件时就停止。

结语

正则表达式,如瑞士军刀般多功能而精巧,它在字符串的密林中开辟路径,既是文本处理的利器,也是数据挖掘的宝藏钥匙。掌握这把万能钥匙,解锁编程与数据处理的无限可能。

上一篇:Oracle 性能优化的高频面试题及答案


下一篇:【STM32开发笔记】移植AI框架TensorFlow到STM32单片机【上篇】-二、TFLM开源项目