写正则表达式最让人抓狂的正则,不是表达语法本身,而是式语每次调试时那种“明明看着没问题,跑起来就是正则不对”的瞬间。上周帮同事排查一个日志解析脚本,表达他用
\d{ 4}-\d{ 2}-\d{ 2}去匹配日期,式语结果把2024-13-45这种非法日期也放过去了。正则问题不在正则引擎,表达在于他把“格式匹配”和“逻辑校验”混为一谈。式语正则表达式只管字符长什么样,正则不管内容有没有意义。表达字符类是式语最基础的东西。
[abc]匹配 a、正则b、表达c 中任意一个;[^abc]反过来,式语匹配除了这三个之外的任何字符。范围写法[a-z]很常见,但要注意[a-Z]这种写法在多数引擎里会报错或者行为诡异,因为 ASCII 码表里大写字母在小写字母前面,范围顺序反了。我习惯用[A-Za-z]明确写开,省得踩坑。量词决定重复次数。
*是零到多次,+是一到多次,?是零或一次,{ n}精确 n 次,{ n,}至少 n 次,{ n,m}在 n 到 m 之间。贪婪模式是默认行为,.*会一路吃到行尾再回溯。加个问号变成.*?就切换成懒惰模式,匹配到第一个满足条件的位置就停。处理 HTML 标签时这个区别特别明显:<.*>会把整行所有标签一口气吞掉,<.*?>才是一个一个来。锚点和边界容易被忽视。
^匹配行首,$匹配行尾,\b是单词边界,\B是非单词边界。有个经典案例:用\bcat\b去匹配 “concatenate” 里的 “cat” 是匹配不上的,因为前后都是字母,没有边界。但\bcat\b能匹配 “a cat sat” 里的 “cat”。这个细节在做关键词提取时很关键。分组和引用是进阶内容。圆括号
()把一部分模式包起来,既可以整体加量词,也能捕获内容供后面引用。(ab)+匹配 “ababab”。反向引用用\1、\2这种写法,比如(['"]).*?\1可以匹配成对的引号。非捕获分组(?:...)只分组不编号,性能稍好,也不需要反向引用时用它更干净。命名分组(?<name>...)在 Python 和 .NET 里支持,JavaScript 从 ES2018 也开始支持,写复杂正则时可读性提升明显。零宽断言是我觉得最值得花时间掌握的部分。
(?=...)是正向先行断言,(?!...)是负向先行断言,(?<=...)是正向后行断言,(?<!...)是负向后行断言。它们只匹配位置,不消耗字符。比如要匹配后面跟着 “元” 的数字,用\d+(?=元),结果里不会包含 “元” 字。后行断言在 JavaScript 里曾经长期缺失,直到 ES2018 才补上,之前写密码强度校验时只能绕路。不同语言的正则实现差异不小。JavaScript 的
\d只认 ASCII 数字,Python 的re模块默认也是,但加上re.UNICODE或直接用regex库就能匹配全角数字。PCRE 系(PHP、Perl)的递归模式(?R)能匹配嵌套括号,JavaScript 至今没有原生支持。写跨语言的正则时,我一般先在 regex101.com 上选好对应的 flavor,省得来回改。最后说个实用习惯:复杂正则拆成多行,用
顶: 89664踩: 6x标志(Python 的re.VERBOSE,JavaScript 没有原生支持但可以拼字符串)加注释。一个二十行的正则如果挤成一行,三天后自己都看不懂。正则不是越短越好,可维护性比炫技重要得多。






评论专区