正则表达式基础:元字符、字符类、量词、分组

说实话,很多新手看到正则表达式就头大。一堆奇奇怪怪的符号挤在一起,看着像乱码。但我做了这么多年模式生成,必须说一句——正则表达式是你最值得花时间掌握的工具之一。它就像文本世界的瑞士军刀,打开一次用一辈子。

我个人习惯把正则表达式拆成四块来学:元字符、字符类、量词、分组。一块一块啃,别想一口吃成胖子。今天我就带你把这四块掰开揉碎。

一句话总结:正则表达式就是用「模式字符」去匹配文本中的「目标字符」。

正则表达式基础四块 正则表达式核心 元字符 . ^ $ * + ? { } [ ] \ | ( ) 字符类 [abc] [0-9] \d \w \s 量词 * + ? {n} {n,} {n,m} 分组 ( ) (?: ) 回溯引用 四块组合使用 → 可匹配几乎任何文本模式 ↑ 箭头流向:先理解元字符,再掌握量词和字符类,最后用分组收尾 实际案例: 匹配邮箱:^[\w.-]+@[\w.-]+\.\w{2,}$ → 元字符:^ $ . | 字符类:[\w.-] \w | 量词:+ {2,} | 分组:(无) 匹配HTML标签:<([a-z]+)([^<]*)(?:>.*</\1>|\s?/>) → 这里面用了分组、回溯引用、非捕获分组,全齐了

元字符:正则表达式的字母表

元字符是什么?说白了就是有特殊含义的字符。你想想看,在普通文本里,点号(.)就是个点,但在正则里,它代表「任意一个字符」。这就是元字符——它不再是它自己了。

我刚开始学的时候,最头疼的就是记这些符号。后来我发现一个简单的办法:把正则表达式里的符号和普通文本的符号分开看。你在写文字时候用「.」就是句号,但在写模式的时候,它就是不包含换行的任意字符。

元字符 含义 示例 匹配结果
. 匹配除换行外的任意一个字符 h.t hat, hit, hot
^ 匹配字符串开头 ^Hello 只会匹配开头的 Hello
$ 匹配字符串结尾 world$ 只会匹配结尾的 world
| 逻辑或 cat|dog 匹配 cat 或 dog
\ 转义字符 \. 匹配真正的点号

我的经验:写正则最常犯的错就是忘了转义。比如你要匹配一个点号,直接写「.」的话它会匹配任意字符。我曾经调试一个日志解析脚本,花了两小时才发现就是少了个反斜杠。所以记住:要匹配元字符本身,前面加反斜杠。

字符类:一次匹配多个可能性

字符类用方括号 [ ] 包裹。它表示「匹配括号内的任意一个字符」。你想想看,如果你要匹配所有元音字母,写 [aeiou] 就行了,比写五个「或」要简洁得多。

字符类有几种常用写法:

  • 范围表示:[a-z] 匹配所有小写字母,[0-9] 匹配所有数字
  • 取反:[^0-9] 匹配任何不是数字的字符(注意:这里的 ^ 在方括号里是取反,不在开头了)
  • 预定义字符类:
    • \d — 等价于 [0-9]
    • \w — 等价于 [a-zA-Z0-9_](字母、数字、下划线)
    • \s — 等价于 [ \t\n\r\f](空白字符)
    • \D、\W、\S — 分别是它们的取反
# 举个实际例子
# 你想匹配一个简单的电话号码:3个数字-4个数字
# 用字符类写就是
\d{3}-\d{4}
# 或者写成
[0-9]{3}-[0-9]{4}
# 两种都可以,但 \d 更简洁

量词:控制重复次数

量词解决的是「这个模式要出现多少次」的问题。没有量词的话,你每写一个字符都只能匹配一次,那也太惨了。

常见的量词有这么几个:

量词 含义 示例 匹配
* 0次或多次 ab*c ac, abc, abbc
+ 1次或多次 ab+c abc, abbc(不匹配 ac)
? 0次或1次 ab?c ac, abc
{n} 恰好 n 次 \d{4} 只能匹配4位数字
{n,} 至少 n 次 \d{3,} 3位或更多数字
{n,m} n 到 m 次 \d{2,4} 2到4位数字

曾经踩过的坑:贪婪匹配和懒惰匹配。默认情况下,量词是「贪心的」,它会把能匹配的都吃掉。比如你用 <.*> 去匹配 <div>内容</div>,它会把整行都吃掉,而不是只匹配第一个标签。解决方法就是加个 ?,写成 <.*?>,变成非贪婪模式,匹配到第一个就停。

分组:把模式组织起来

分组用小括号 ( )。它的作用有三个:

  1. 改变优先级:(cat|dog)* 先选 cat 或 dog,再决定重复
  2. 提取子匹配:被括号包起来的部分,匹配结果可以被单独拿出来用
  3. 回溯引用:在同一个正则里引用之前匹配到的内容

回溯引用特别有用。举个例子,你要匹配重复的单词:

# 匹配重复的单词(比如 "the the")
\b(\w+)\s+\1\b
# 这里的 \1 引用了第一个括号里匹配到的内容
# 如果前面匹配了 "the",\1 就是 "the"
# 这样就能抓住 "the the" 这种重复

我记得有一次写一个数据清洗脚本,需要把 HTML 标签成对替换。用回溯引用就特别方便:

# 匹配成对的 HTML 标签
<(\w+)>.*?</\1>
# \1 会记住标签名,保证开闭标签匹配
# 比如 <div>内容</div> 能匹配
# 但 <div>内容</span> 不能匹配

小技巧:如果你只需要分组功能但不想保存子匹配,可以用非捕获分组 (?: )。这样能提升性能,尤其是在大量文本匹配时。我自己写工具类的正则时,只要括号里的内容不需要单独拿出来,一律用非捕获分组。

综合实例:验证邮箱地址

把上面四块知识凑一起,咱们写个邮箱验证的正则:

^[\w.-]+@[\w.-]+\.\w{2,}$

拆开看:
1. ^              — 元字符,字符串开头
2. [\w.-]+        — 字符类+量词,用户名部分,允许字母数字下划线点号和连字符
3. @              — 普通字符,真正的 @
4. [\w.-]+        — 域名部分
5. \.             — 转义后的点号
6. \w{2,}         — 顶级域名,至少2个字母
7. $              — 元字符,字符串结尾

来看几个测试案例:

输入 结果 原因
a@b.com 匹配 标准格式
user.name@domain.co.uk 不匹配 第二个点号后还有内容,我们的正则只允许一个点号
invalid-email 不匹配 没有 @ 和域名

你看,一个简单的正则就能解决很多实际问题。写的时候要注意,别让模式太严格,也别太宽松。我一般先写一个宽松版本,然后根据实际数据慢慢收紧。毕竟正则这个东西,写死了匹配不到,写松了乱匹配,分寸要自己把握。