从入门到"瞎眼"
最初的正则写法,简直是小学生数学:
- 匹配数字:
[0-9]
- 匹配字母:
[a-zA-Z]
- 匹配单词:
\w+
简单直观,还能说得清。
结果有一天,你在项目里看到这么一坨:
ruby
^(?=.*[A-Z])(?=.*\d)[A-Za-z\d!@#$%^&*]{8,}$
你大概会愣住:这不是我认识的正则!
原来别人用了一堆零宽断言,写了个"密码强度验证",瞬间从"简单匹配"变成了"暗黑魔法"。
当正则开始玩心理战
正则真正的杀伤力,不是写出来,而是读别人写的。
比如说,以下两个表达式都能验证邮箱:
ini
^[^@\s]+@[^@\s]+.[^@\s]+$
ruby
^(?=.{1,64}@.{4,255}$)(?=.{6,})([A-Za-z0-9._%+-]+)@([A-Za-z0-9.-]+).([A-Za-z]{2,})$
前者"能用",后者"较严谨",但阅读体验完全不同。
有些人甚至会把正则写成一幅 艺术品,加上各种嵌套、条件分支,让你怀疑人生。
锚点(Anchors)
^
:匹配字符串的 开始 如果写在中间位置,需要转义\^
才能表示字面量^
。$
:匹配字符串的 结束
正则 | 意义 |
---|---|
^123 |
匹配以 123 开头的字符串 |
[^123] |
匹配不是 1 、2 、3 的任意一个字符 |
123$ |
匹配以 123 结尾的字符串 |
常用占位符(Metacharacters)
\d
- 含义 :匹配一个数字
[0-9]
- 例子 :
\d{3}
→ 匹配任意三位数字,如123
\D
- 含义:匹配一个非数字字符
- 例子 :
\D+
→ 匹配连续的非数字,如abc
\w
- 含义 :匹配一个单词字符(字母、数字或下划线
[A-Za-z0-9_]
) - 例子 :
\w+
→ 匹配单词,如hello_123
\W
- 含义:匹配一个非单词字符
- 例子 :
\W+
→ 匹配标点或符号,如!@#
\s
- 含义:匹配一个空白符(空格、换行、制表符等)
- 例子 :
a\s+b
→ 匹配a b
、a b
\S
- 含义:匹配一个非空白符
- 例子 :
\S+
→ 匹配非空字符序列
.
- 含义 :匹配除换行符
\n
之外的任意单字符 - 例子 :
a.c
→ 匹配abc
、a-c
、a1c
?
- 含义:表示前一个子表达式可有可无(0 次或 1 次);也可用于懒惰匹配
- 例子 :
colou?r
→ 匹配color
或colour
\n
- 含义:匹配一个换行符
- 例子 :
foo\nbar
→ 匹配foo
换行后接bar
\t
- 含义:匹配一个制表符
- 例子 :
\t123
→ 匹配前面有一个 Tab 的123
\b
- 含义:匹配一个单词边界(单词与空白/符号之间的位置)
- 例子 :
\bcat\b
→ 匹配完整的cat
,不匹配concatenate
\B
- 含义:匹配一个非单词边界
- 例子 :
\Bcat\B
→ 匹配在单词中间出现的cat
,如concatenate
示例:匹配 QQ 邮箱
regex
/^[1-9][0-9]{5,11}@qq\.com$/
# 正则表达式量词总结
量词
*
- 语法 :
\d*
- 含义 :数字可以出现 0 次或任意多次(包括不出现)。
+
- 语法 :
\d+
- 含义 :数字至少出现 1 次,可以出现多次。
?
- 语法 :
\d?
- 含义 :数字 出现 0 次或 1 次。
{n}
- 语法 :
\d{2}
- 含义 :数字必须 恰好出现 n 次(此处为 2 次)。
{n,m}
- 语法 :
\d{2,4}
- 含义 :数字至少出现 n 次 ,至多出现 m 次(此处为 2--4 次)。
{n,}
- 语法 :
\d{3,}
- 含义 :数字至少出现 n 次(此处为 ≥3 次)。
⚠️ 注意 :
这些量词本身不能直接跟数字写在一起,否则会被解释成数量,而不是正则语法。
例如:
- 正确:
\d{2,4}
- 错误:
\d2,4
正则表达式常用符号速查表
^
- 含义 :匹配输入字符串的 开始位置。
- 特殊情况 :在
[]
中使用表示 非 (取反)。- 例:
^[A-Z]
→ 以大写字母开头 - 例:
[^0-9]
→ 非数字
- 例:
$
- 含义 :匹配输入字符串的 结尾位置。
- 多行模式 :当设置
m
(multiline) 标志时,$
也可以匹配换行符前的位置。- 例:
abc$
→ 以 "abc" 结尾的字符串
- 例:
()
-
含义:标记一个子表达式的开始和结束。
-
作用 :
- 分组:将多个字符当成一个整体。
- 捕获:匹配到的子串可用于后续引用。
- 例:
(abc|bac|acd)
→ 匹配三者之一
*
- 含义 :匹配前面子表达式 零次或多次 。
- 例:
a*
→""
、a
、aa
、aaa
...
- 例:
+
- 含义 :匹配前面子表达式 一次或多次 。
- 例:
a+
→a
、aa
、aaa
...
- 例:
?
- 含义 :匹配前面子表达式 零次或一次 ;或作为 非贪婪限定符 (
*?
/+?
)。- 例:
a?
→""
、a
- 例:
.
- 含义 :匹配除换行符
\n
之外的 任意单个字符 。- 例:
a.c
→ 匹配abc
、a-c
、a1c
等
- 例:
[]
- 含义 :定义一个 字符类,匹配方括号中的任意一个字符。
- 取反 :
[^...]
表示不在集合内的任意一个字符。- 例:
[abc]
→ 匹配a
、b
或c
- 例:
[^0-9]
→ 匹配非数字
- 例:
{}
- 含义:标记限定符,指定前面子表达式的重复次数。
- 语法 :
{n}
→ 恰好 n 次{n,m}
→ n 到 m 次{n,}
→ 至少 n 次- 例:
\d{2,4}
→ 2 到 4 位数字
|
- 含义 :逻辑"或",匹配两个表达式中的一个。
- 例:
cat|dog
→ 匹配cat
或dog
- 例:
\
-
含义:转义符。
-
作用 :
- 将特殊字符转为普通字符:
\.
→ 匹配点号本身 - 表示特殊序列:
\d
数字,\w
单词字符,\s
空白 - 表示反向引用:
\1
→ 引用第一个捕获组
- 例:
a\.
→ 匹配a.
- 例:
(\d)\1
→ 匹配连续两个相同数字
- 将特殊字符转为普通字符:
正则表达式修饰符(Flags)
i
- 含义:不区分大小写匹配。
- 例子 :
/abc/i
→ 匹配abc
、ABC
、AbC
m
- 含义:多行模式。
- 效果 :
^
和$
不仅匹配整个字符串的开头和结尾,还能匹配每一行的开头和结尾。 - 例子 :
/^abc/m
→ 在多行文本中匹配行首的abc
s
- 含义 :单行模式,使
.
可以匹配包括换行符在内的所有字符。 - 例子 :
/a.b/s
→ 匹配a
换行b
u
- 含义 :启用 Unicode 模式,正确处理 Unicode 字符(如 emoji、特殊符号)。
- 例子 :
/^\p{L}+$/u
→ 匹配所有 Unicode 字母
y
-
含义 :粘性匹配,从字符串的当前位置开始匹配,不会跳过字符。
-
例子 :
jsconst r = /\d/y; r.lastIndex = 2; console.log(r.test("12")); // false,因为不会回溯
预查
?!
-
名称:负向先行断言(Negative Lookahead)
-
含义 :匹配某个位置,要求 后面 的内容不满足条件。
-
示例 :
iniconst str = "photo.jpg image.jpg doc.txt"; str.match(/[a-z]+[.]+(?!jpg)/g); // ["doc."]
?<=
-
名称:正向后行断言(Positive Lookbehind)
-
含义 :匹配某个位置,要求 前面 的内容满足条件。
-
示例 :
pythonconst str = "photo.jpg image.jpg"; str.match(/\w+(?<=\w).jpg/g); // ["photo.jpg", "image.jpg"]
?<!
-
名称:负向后行断言(Negative Lookbehind)
-
含义 :匹配某个位置,要求 前面 的内容不满足条件。
-
示例 :
iniconst str = "photo.jpg image.jpg"; str.match(/\w+(?<!photo).jpg/g); // ["image.jpg"]
贪婪(Greedy)
-
定义 :量词(
* + {n,m}
等)默认是 贪婪的,会尽可能多地匹配字符。 -
示例 :
jsconst str = "<div>content</div><div>test</div>"; str.match(/<div>.*<\/div>/); // 结果: ["<div>content</div><div>test</div>"] // 贪婪匹配,把中间的所有内容都吃掉了
惰性(Lazy / Reluctant)
-
定义 :在量词后加
?
(如*? +? {n,m}?
),会变成 惰性匹配,尽可能少地匹配字符。 -
示例 :
jsconst str = "<div>content</div><div>test</div>"; str.match(/<div>.*?</div>/g); // 结果: ["<div>content</div>", "<div>test</div>"] // 惰性匹配,逐段匹配,不会一次性吞掉所有
怎么与安全的使用正则
正则就像一把双刃剑:
- 用得好:简洁、强大、跨语言通用,一行搞定复杂逻辑。
- 用得烂:可读性差、性能糟糕、出 bug 难调试,团队没人敢维护。
于是有人调侃:
"正则就是程序员的黑魔法:写的时候感觉自己是神,过几个月回头看,感觉自己是受害者。"
-
写注释:复杂表达式最好分段解释。
ruby^(?=.*[A-Z]) # 至少一个大写字母 (?=.*\d) # 至少一个数字 [A-Za-z\d]{8,}$ # 总长度≥8
-
拆逻辑:别硬要一个正则全搞定,允许前后分步校验。
-
工具辅助 :用 regex101.com 这种网站实时调试。
-
团队约定:常用正则做成函数封装,别到处复制粘贴。
-
AI审查 :使用AI工具验证
有些AI可不准哦
。