
正则表达式这东西说难是真难说简单也真简单。难在它语法抽象、符号又多初次接触基本等于看天书简单在于它本质上就干一件事——描述字符串的模式。我见过太多人一翻开正则教程就被各种符号劝退又或者用过一段时间但始终一知半解每次都是现搜现查。这篇总结我写了挺久把入门到进阶的知识点压成一条清晰可见的主线配合大量短例子争取让你读完就能上手写完后能把常用的正则场景拿捏住。1. 正则表达式的核心思想1.1 它不是“编程语言”而是一种字符串模式语言我在给别人讲正则的时候最怕听到的一句话就是“正则好难跟写代码不一样”。确实不一样但你要换个角度正则不是用来计算、不是用来存储数据的它存在的全部意义只有一个——定义一种规则然后用这个规则去扫描一段文本找出符合规则的子串。你可以把正则想象成一张“文字画像”。这张画像描述的不是某个固定的句子而是一类特征。比如你心里想着“我要找所有以字母a开头、后面跟着任意数字的单词”那这个画像就可以写成a\d*\b。正则引擎拿到这个画像就会替你在一大段文本里把这些词全部揪出来。所以学正则的第一步不是死记符号而是建立“模式思维”。看到任何一段文字你先想它的共性是什么开头是什么字符中间有多少位结尾有什么特征这种思考方式比记住三十个符号重要得多。1.2 正则的适用面比你想象的广说实话正则早就不只是程序员的工具了。做数据分析的同学拿它清洗脏数据办公人群拿它做文本批量替换编辑用它做关键词校对运维拿它过滤日志。甚至很多软件的高级搜索框、表单校验、路由匹配底层都跑着正则。有一点需要提前说清楚不同语言、不同工具里的正则写法大体一致但细节上有差异。比如\d这个符号在多数语言里代表数字但有些老旧的工具或语言默认它不是再比如反向引用的写法在多数语言里是\1但JS里如果要引用命名分组要用\kname。入门阶段你没必要纠结这些差异先用一套主流语法比如JavaScript、Python、Java的都行吃透后面换工具时查一次差异表就能轻松迁移。1.3 学习路径建议我总结了一套适用于绝大多数人的入门路径先学基础字符匹配再学字符集和量词第三学边界匹配第四学分组与捕获最后学断言与贪婪模式。这条路径的特点是每个阶段都有强烈的正反馈不会卡在半路上。这篇总结就按照这条路径来走。每一节后面我都放了一两个可以直接拿去跑的小例子建议你跟着敲一遍因为正则这东西永远“看会”不如“写会”。2. 基础字符匹配正则的积木单元2.1 原样匹配与转义字符正则里最朴素的规则就是“原样匹配”。你在正则里写cat引擎就去文本里找cat这个词一个字母都不差。这种规则简单直接但用处有限因为真正要匹配的东西往往带有很多不确定的部分。于是有了转义字符的概念。先记住一句话元字符想要表示它本身就必须在前面加反斜杠\。比如你要匹配一个小数点.不能直接写.因为.是正则里的“任意字符”你必须写成\.才能匹配字面上的句号。同理匹配星号要写\*匹配反斜杠本身要写\\。初学阶段最容易踩的坑就是忘记转义。我见过有人想匹配网址里的点号写了www.baidu.com去匹配原文结果发现啥都能匹配上。这里做个提醒如果你要匹配的目标中包含.*?()[]{}^$\或|先想想它们在正则里是不是特殊符号是的话就得转义。2.2 任意字符与占位符英文句点.在正则里的含义是“任意一个字符”换行符除外某些模式可以改变这个行为。这是一个高频符号但它也是新手最容易误用的符号。你写a.c能匹配abc、a1c、a c但匹配不了ac因为点号必须占一位。占位符的概念非常重要。你可以把正则里的每个字符都当成一个“座位”字符代表固定的人点号代表“谁来都行但必须来一个”。理解了占位逻辑正则的位次关系就清楚了一半。有一条经验值得分享用.匹配任意字符时尽量精确到“必须出现的结构”。比如你要匹配手机号中间的某段如果确定是三位就写三个\d不要写...因为点号还会把不该匹配的标点也匹配进去。2.3 预定义字符类下表是入门必须背下来的基础也是平时用得最频繁的一组写法含义等价字符集写法反义写法含义\d数字0-9[0-9]\D非数字\w字母、数字、下划线[A-Za-z0-9_]\W非单词字符\s空白空格、制表、换行等[\t\n\r\f ]\S非空白\d、\w、\s这三组一定要做到“看见就反应出含义”这是正则里最常用的缩写。实际匹配中文时\w通常匹配不了汉字需要在字符集里显式加a-zA-Z\u4e00-\u9fa5或者用相应语言的Unicode属性这一点我们后面专门讲。2.4 实际示例匹配日期假设文本里有“2024-05-12”、“2024/5/12”、“May 12, 2024”三种格式你要把所有日期都找出来。第一轮最简单先处理带横杠的完整格式\d{4}-\d{2}-\d{2}这条规则的意思是四个数字、横杠、两个数字、横杠、两个数字。{4}是量词我们现在还没正式讲但你能猜出来它是“前面前面那个符号重复4次”。这就是正则直观的地方——它是在描述结构不是抽象指令。3. 字符集与范围匹配3.1 方括号的灵活匹配方括号[]用来定义“字符集合”意思是“匹配括号中列出的任意一个字符”。[abc]能匹配a、b、c中的任意一个。单个字符的规则在这里体现得很清楚一个方括号不管里面写了多少候选最终只匹配一个字符。范围可以用连字符-来简化[0-9]等价于\d[a-z]匹配任何小写字母[A-Z]匹配大写字母[a-zA-Z]忽略大小写地匹配所有字母。还可以混着写比如[0-9a-fA-F]能匹配十六进制字符。3.2 排除字符集方括号的进阶用法是在开头加^表示“排除”。[^0-9]表示匹配非数字字符[^abc]表示匹配除 a、b、c 之外的任意字符。注意这个^写在括号内和写在正则最前面的含义完全不同——最前面的^表示“以...开头”是位置的锚点不是字符排除。排除集合我常拿来做“脏数据过滤”。比如要从一段文本里把不是数字开头的行找出来就直接写^[^0-9]。这个规则看起来绕多写几次就顺了。3.3 字符集用到的转义在字符集内部某些符号的转义规则和外面不一样。-在字符集里如果不是首尾位置会被当成范围符号要匹配字面上的-要么写\-要么把它放在字符集首位或末位比如[-0-9]或[0-9-]。]在字符集内也需要转义写\]。新手常遇到一个坑在字符集里用^做排除时如果^不是第一个字符那就只是一个普通字符。比如[a^b]匹配的是 a、^、b 三者之一而不是“排除a和b”。3.4 匹配中文字符的通用写法在不少语言里用正则匹配中文要显式指定范围。通用的写法是[\u4e00-\u9fa5]这个范围对应汉字Unicode编码的主要区块。不过要注意这个范围并不完全覆盖扩展区的生僻字但对于日常绝大多数场景是够用的。如果使用Python的re模块还可以用[\u4e00-\u9fa5]加上re.UNICODE标志位处理效果更稳定。有一个经验处理中文时尽量少用.来匹配字符。因为有些正则引擎的.默认不匹配中文标点之外的某些Unicode字符容易漏匹配直接用字符集最稳妥。4. 量词重复与数量控制4.1 三种基础量词正则里的量词解决一个核心问题一个符号到底要重复多少次才算匹配成功。量词含义*出现0次或多次出现1次或多次?出现0次或1次你可能发现*和?都包含“可以不出现”的情况。这里的区别在于*且出现无限次都行?最多只能出现1次。举个例子匹配ab*c那么ac、abc、abbc、abbbc都能通过而匹配ab?c只有ac和abc能通过。4.2 精确次数与大括号如果你要精确控制循环次数就要用花括号{}。{3}表示前面字符出现3次{2,4}表示出现2到4次{3,}表示至少3次上不封顶{,5}表示最多5次。这套符号最典型的应用场景就是手机号码、身份证、日期等固定长度数据。比如中国手机号宽松匹配可以写成1[3-9]\d{9}这里1[3-9]保证了开头是1且第二位是3到9之间的数字后面再接9位数字总共11位。你看到了量词让正则从“找特征”变成“按规格校验”。4.3 贪婪、懒惰与占有默认情况下所有量词都是贪婪的意思是能多匹配就多匹配。举一个经典例子文本是bhello/biworld/i正则.理论上会从第一个一直匹配到最后一个因为它贪心。这就造成一个问题你想匹配单个标签结果把整段全吞了。解决贪婪问题的方法是在量词后面加一个?变成懒惰模式.?。这时候引擎会每次只匹配最少的内容所以它匹配出来的是b、/b、i、/i四个独立标签。这个知识点太重要了我在解析HTML片段、处理日志结构时几乎每次都要用。再强调一次贪婪匹配是默认懒惰匹配是量词?不要搞混。还有一种占有模式写法是在量词后加比如.*。它和贪婪一样尽量多匹配但匹配完不回头不回溯。它主要用在性能敏感的匹配场景避免灾难性回溯。入门阶段可以了解不用刻意记。5. 边界匹配与位置锚定5.1 行首与行尾^默认表示“字符串开头”$表示“字符串结尾”多行模式下则是每行的开头与结尾。组合写法^正则内容$是常见的“整体匹配”写法用于严格校验。举个例子校验一个字符串是否为“纯数字且长度为5”^\d{5}$如果没有^和$正则引擎会在任意长文本中去找满足条件的子串导致“abc12345def”这种字符串也能通过校验。这一点在做表单校验时特别值得注意很多数据校验漏洞就是锚点缺失导致的。5.2 单词边界\b匹配的是位置不是字符。它表示“单词边界”一边是单词字符\w另一边是非单词字符。比如正则\bcat\b能匹配 “a cat sat” 里的 cat但匹配不了 “concatenate” 里的 cat因为后者前面是 o单词字符不构成边界。\B则是非边界。比如\Bcat\B可以匹配 “concatenate” 里的 cat但匹配不了独立的单词 cat。这个知识点在搜索时很实用——你想精确匹配某个英文单词而不是它出现在其他词中间的情况就给它加上\b。5.3 综合示例日志行筛选假如你有一堆日志格式是2024-05-12 10:23:45 ERROR user login failed 2024-05-12 10:24:11 INFO user login success你现在只想要“ERROR”开头的行其实是在行的中间。可以直接利用边界和锚点^.*\bERROR\b把\bERROR\b两边的边界加上避免匹配到ERROR_CODE这类衍生词。用行首锚点^来约束日志从开头匹配。这个思路在日志分析场景非常实用。6. 分组、捕获与反向引用6.1 圆括号的分组意义圆括号()第一个作用是“分组”把多个字符捆成一个整体配合量词使用。比如(ab)能匹配ab、abab、ababab而没有括号的ab只匹配a后面跟一堆b。分组还有一个作用是“捕获”。匹配成功后括号内的内容会被单独存起来供后续提取或反向引用。6.2 捕获组编号与命名按(出现的顺序捕获组自动编号为1、2、3……。第0组通常是整个匹配结果。比如正则(\d{4})-(\d{2})-(\d{2})匹配2024-05-12后group(1) 是2024group(2) 是05group(3) 是12。在各语言里提取捕获组有固定API。比如Python中import re m re.search(r(\d{4})-(\d{2})-(\d{2}), 2024-05-12) print(m.group(1)) # 2024 print(m.groups()) # (2024, 05, 12)命名分组更利于阅读和维护。写法是(?Pyear\d{4})这种Python风格或(?year\d{4})多数其他语言风格。命名分组的核心价值在复杂规则里体现得非常明显——靠数字编号容易数错尤其括号一多数错一位就是灾难。6.3 反向引用匹配重复内容反向引用指的是在正则内部引用前面捕获到的内容。写法是\1、\2或(?Pname)Python命名引用。最常见的场景是匹配连续重复的字符。比如(\w)\1能匹配aa、bb、11这种连续重复的字符。而匹配成对标签可以写(\w).*?/\1这个例子把分组、反向引用、懒惰匹配全部串起来了。它能匹配divcontent/div这样的结构并且能保证闭合标签名和开标签名一致比硬写死标签名灵活得多。反向引用是正则从“简单匹配”跨入“结构化处理”的标志性功能。建议你练熟。6.4 非捕获分组有时候你只想分组不想捕获比如为了配合量词不想让括号内容占一个组号可以用(?:...)。举个例子(?:ab|cd)它能匹配ab或cd重复组成的串但不会把这部分内容捕获到变量里。好处是编号更干净性能稍好。规范一点的团队还会要求不需要提取的分组一律用非捕获格式避免后续维护时数错编号。7. 断言与零宽匹配7.1 什么是断言断言是一类特殊结构它不消耗字符只表达“当前位置是否符合某种条件”。换句话说匹配引擎在扫描到某个位置时先看看这个位置的前方或后方是否满足条件满足则匹配成功但不吞掉任何字符。我常用生活类比断言像“暗号”——你走过一段路暗号对得上就通过但对暗号本身不占你走路的时间你还是要走完这一段路。7.2 四种基础断言写法名称含义(?...)正向先行断言当前位置后面要匹配...(?!...)负向先行断言当前位置后面不要匹配...(?...)正向后行断言当前位置前面要匹配...(?!...)负向后行断言当前位置前面不要匹配...用例子说明。匹配“后跟数字的字母串”[a-z](?\d)它能匹配abc在abc123中但不能匹配abcxyz中的abc因为后面没有数字。关键是它匹配的只是abc123不被消耗掉。再看负向先行断言匹配不以数字结尾的单词\b[a-z](?!\d)\b7.3 断言的典型应用最常见的断言场景是“密码强度校验”。比如要求密码长度为8-16位且必须同时包含字母和数字^(?.*[a-zA-Z])(?.*\d)[a-zA-Z\d]{8,16}$这里面用了两个正向先行断言(?.*[a-zA-Z])保证存在字母(?.*\d)保证存在数字最后再用一个整体规则限制字符集和长度。这种写法很经典我几乎在所有涉及密码校验的项目里都会用。7.4 断言匹配的坑后行断言的兼容性是最参差的。在JavaScript的老版本引擎里(?...)一度不支持Python的re模块要求后行断言必须是固定长度。如果你想兼容所有主流环境最好优先使用先行断言或改成捕获组配合程序判断。还有一个常见误区断言中的.*是贪婪的在复杂文本里可能导致性能下降。比如^(?.*[a-zA-Z])(?.*\d)在超长输入下会重复扫描多次。严格来说这不是坑是性能放大问题。高并发场景建议换一种校验算法正则不是唯一解。8. 替换、修饰符与常用工具8.1 替换与引用正则的一大实用场景是“查找并替换”。替换时捕获组可以通过引用交给替换字符串使用。Python里用\1、\2JavaScript里是$1、$2。比如把日期格式从2024-05-12换成2024/05/12import re text 今天是2024-05-12 result re.sub(r(\d{4})-(\d{2})-(\d{2}), r\1/\2/\3, text) print(result) # 今天是2024/05/12这种操作在处理批量数据时简直救命。以前我在清洗一批用户导入的Excel日期时几十种格式五花八门就是用类似上面的正则统一转化的。8.2 常用修饰符修饰符又称模式修饰符或标志位。最常见的有i忽略大小写g全局匹配查所有不是只查第一个m多行模式^和$匹配每行的开头结尾s点号匹配所有字符包括换行符x忽略空白与注释某些语言支持多行模式在日志处理里非常常用。比如在Python中import re log ERROR : a INFO : b ERROR : c matches re.findall(r^ERROR.*$, log, re.MULTILINE) print(matches) # [ERROR : a, ERROR : c]如果没有re.MULTILINE这个^只会匹配整个字符串的开头结果只能找到第一条。8.3 在线工具与本地工具我平时用得最多的在线工具是 regex101 和 regexr。前者有逐步匹配可视化、分组对照表、正则语法说明写复杂正则时我基本离不开它。本地环境里VS Code 的搜索框自带正则模式特别适合对单个文件做批量重构命令行里的grep -E也支持扩展正则。不管你用什么工具建议至少要掌握一种“可视化-调试-粘贴回代码”的工作流。不要直接在代码里盲写长正则排错效率太低了。9. 常见错误与排查方法9.1 括号不匹配写复杂正则时左右括号最容易配错。IDE的语法高亮和在线工具的配对提示能帮你快速找到问题。排查时有个技巧一个个去掉分组从简到繁逐步构建而不是一口气写完再找bug。9.2 特殊字符忘了转义.、*、、?、(、)、[、]、{、}、^、$、\、|都是元字符。要是想匹配它们的字面含义必须用\转义。排查“为什么匹配结果不精确”时第一反应就应该是检查元字符。9.3 贪婪匹配导致超范围“匹配结果太长”是新手最常反馈的问题。贪婪量词导致整个匹配跨越了不该跨越的部分。解决办法是改用懒惰量词*?或?或者用排除字符集替代点号。比如匹配引号内的内容可以写[^]*比.*?更高效也更精确。9.4 锚点缺失导致子串误匹配校验类正则如果忘记写^和$往往会出现“部分匹配”通过校验的情况。拿日期校验举例正确的整体校验应该是^\d{4}-\d{2}-\d{2}$不加锚点的话字符串里任何一段符合日期格式都会被放行。9.5 一个排查思路我一般会用“分治法”排查复杂正则出错先把正则拆成几个子正则逐个对样例文本测试确定每个部分的行为后再逐步组合。这个过程配合在线工具的定位高亮通常几分钟就能锁定问题区间。10. 实操案例邮箱校验与URL提取10.1 邮箱校验入门到中等我们来写一个模块邮件地址的校验正则从简到繁演进。最简单的版本\w\w\.\w能匹配nameexample.com。但实际问题很多用户名部分可能有点号、邮箱域名可能有多级比如first.lastmail.example.com.cn。改进版仍不追求完美但可应对常见场景^[\w.-][\w-](\.[\w-])$逐一解释[\w.-]表示用户名部分允许字母数字下划线、点号、加号、减号是分隔符[\w-]匹配一级域名(\.[\w-])匹配若干级后缀。注意我用的^...$整体锚定因为这里是校验。如果只是提取不加锚点。10.2 URL提取从一段文本中提取所有链接简化版https?://[\w./?#%-]它匹配http://或https://后面跟域名、路径和查询参数常用的字符。这个正则是实用的“够用版”不会误抓太多无关内容也足够简单到看一眼就懂。更严谨的URL解析其实建议用官方库正则只做预筛选。要记住正则不是万能的处理HTML结构的正确工具是解析器而不是正则这是所有从业者都会告诉你的一句话。10.3 批量清洗从脏数据中提取手机号实战场景有一段完全非结构化文本混着姓名、地址和手机号要提取11位手机号1[3-9]\d{9}不要加^和$因为手机号藏在长文本里。用findall就能一次拿出所有结果。如果担心误匹配比如12位数字中的前11位也会被匹配出来可以加上边界约束(?!\d)1[3-9]\d{9}(?!\d)表示前面不是数位、后面也不是数位确保这11位数字是独立的一串。这种前后断言组合的使用频率非常高可以说掌握了它正则水平就迈过了一道坎。11. 避坑诀窍与经验总结正则写了这么多年有些经验是实打实踩坑踩出来的写在这里供参考。第一不要追求“一行大师”。我经常看到有人炫技式地用一行超级正则搞定复杂解析结果维护时没人看得懂。能用两步拆分解决的问题就不要硬塞进一个正则里。第二写任何校验类正则先定锚点。不确定要不要加^和$时先问自己这个正则用在“整体校验”还是“局部查找”。这两个场景的写法逻辑完全不同。第三优先用排除型字符集替代点号。比如提取引号中的内容[^]*优于.*?前者语义更清晰而且性能更好。第四中文环境下务必留意Unicode匹配。\w在很多语言里不匹配中文需要用[\u4e00-\u9fa5]或者使用支持Unicode属性的表达式如\p{ScriptHan}。不同的数据源会给不同的教训但这一点基本是通用的。第五正则不是越大越好也不是越短越好。有些问题官方库早就解决得很好了比如日期解析用内置字典、HTML解析用DOM树、URL解析用URL类。正则适合做“模式匹配”不适合做“格式解析”。你拿着锤子的时候看什么都像钉子但正则锤子打鈑不了螺丝。第六调试正则时高频使用断言和分组可视化。在线工具能帮你直观理解匹配过程能看到每一步消耗了哪些字符。这是提升正则水平最快的方式没有之一。在这份总结里我刻意没有堆砌太多生僻语法而是把最常用、最能解决问题的核心内容讲透。读完你可以反查自己的掌握情况字符匹配、量词、边界、分组、断言、替换、修饰符这七大块是否都能独立写出示例。如果每一块你心里都已经有一个能跑的案例那你的正则就算是入门成功了。