正则表达式基础:元字符、字符类、量词、分组
说实话,很多新手看到正则表达式就头大。一堆奇奇怪怪的符号挤在一起,看着像乱码。但我做了这么多年模式生成,必须说一句——正则表达式是你最值得花时间掌握的工具之一。它就像文本世界的瑞士军刀,打开一次用一辈子。
我个人习惯把正则表达式拆成四块来学:元字符、字符类、量词、分组。一块一块啃,别想一口吃成胖子。今天我就带你把这四块掰开揉碎。
一句话总结:正则表达式就是用「模式字符」去匹配文本中的「目标字符」。
元字符:正则表达式的字母表
元字符是什么?说白了就是有特殊含义的字符。你想想看,在普通文本里,点号(.)就是个点,但在正则里,它代表「任意一个字符」。这就是元字符——它不再是它自己了。
我刚开始学的时候,最头疼的就是记这些符号。后来我发现一个简单的办法:把正则表达式里的符号和普通文本的符号分开看。你在写文字时候用「.」就是句号,但在写模式的时候,它就是不包含换行的任意字符。
| 元字符 | 含义 | 示例 | 匹配结果 |
|---|---|---|---|
. |
匹配除换行外的任意一个字符 | 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>,它会把整行都吃掉,而不是只匹配第一个标签。解决方法就是加个 ?,写成 <.*?>,变成非贪婪模式,匹配到第一个就停。
分组:把模式组织起来
分组用小括号 ( )。它的作用有三个:
- 改变优先级:
(cat|dog)*先选 cat 或 dog,再决定重复 - 提取子匹配:被括号包起来的部分,匹配结果可以被单独拿出来用
- 回溯引用:在同一个正则里引用之前匹配到的内容
回溯引用特别有用。举个例子,你要匹配重复的单词:
# 匹配重复的单词(比如 "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 |
不匹配 | 没有 @ 和域名 |
你看,一个简单的正则就能解决很多实际问题。写的时候要注意,别让模式太严格,也别太宽松。我一般先写一个宽松版本,然后根据实际数据慢慢收紧。毕竟正则这个东西,写死了匹配不到,写松了乱匹配,分寸要自己把握。