正则表达式核心知识点与常见坑:从字符集合到回溯引用 简介面向互联网、软件开发、数据分析等岗位初学者与进阶者的正则表达式知识点总结已整理为便携PDF电子版。内容源自知名CS-Notes原创笔记重新排版后更适合离线阅读。资源按九个章节展开匹配单个字符、匹配一组字符、使用元字符、匹配空白字符、重复匹配、位置匹配、使用子表达式、回溯引用、前后查找与嵌入条件。匹配单个字符部分重点讲解点号通配和转义处理字符集合中引入区间定义与取非操作元字符部分区分数字、字母数字和空白字符类别重复匹配涵盖贪婪与懒惰型模式位置匹配涉及单词边界和字符串边界子表达式与回溯引用支持复杂模式的复用前后查找和嵌入条件进一步提升文本定位的精准度。书中还包含邮箱地址、IP地址、代码注释、电话号码格式修改等实际案例。包内共1个PDF文件大小约386KB结构紧凑便于快速检索。目前已有326人学习下载。通过阅读该PDF读者可以从零构建正则表达式的完整知识框架掌握日常开发中常用的匹配、替换、校验技巧有效提升文本处理效率。1. 正则表达式这份 PDF 笔记值不值得花一晚上读完正则表达式是互联网文本处理里最容易被低估的一项基本功。写爬虫清洗 HTML、批量改日志、从 PDF 里抽字段、在编辑器里做批量替换都要用到它但大多数人的学习路径是“用到哪查到哪”查完复制、复制完就忘。这份《正则表达式》知识点整理 PDF 源自 CyC2018 的 CS-Notes 仓库PDF 化整理来自 sjsdfg/CS-Notes-PDF把匹配单个字符、字符集合、元字符、重复匹配、位置匹配、子表达式、回溯引用、前后查找到嵌入条件串成了一条完整的学习线。适合两类人一类是写过脚本但正则总是靠猜的从业者另一类是想要一份离线速查手册、随时翻知识点的人。它不教你怎么安装软件也不绑定某种语言而是讲正则本身的规则看完可以直接迁移到 Python、Java、JavaScript 或者编辑器里用。2. 从单个字符到字符集合把最基础的四类匹配场景用熟2.1 点号不是万能通配先看它在字符集合内外的区别正则里面最容易给人“错觉”的元字符就是点号。.可以匹配任意单个字符但绝大多数实现里不能匹配换行符。也就是说在一段多行文本里.默认跨不过行尾。另一个关键点.是元字符它在字符集合[]之内就变回普通字符不需要转义在[]之外想匹配字面上的点号必须写成\.。这份笔记里给了一个很直观的演示用正则Zheng.去匹配文本My name is Zheng.能匹配到Zheng.这个完整片段。因为末尾的点号正好被.消费掉。这个例子虽然简单但它同时说明了两个事正则区分大小写默认情况下zheng.匹配不到以及.确实能匹配一个英文句点。我一般建议初学者先把这句话记住元字符的含义取决于上下文。.在[]外是“任意字符”在[]内就是“一个点”-在[]内是区间符号在[]外就是普通连字符^在[]内是取非在[]外是行首匹配。后面这一条会反复出现也是踩坑重灾区。2.2 字符集合与区间[]、-、^ 三者怎么配合才不翻车字符集合用[]定义它匹配的是“集合内任意一个字符”。比如[abc]匹配 a、b、c 中的任意一个而不是匹配字符串abc。这个认知非常关键很多人第一次写[abc]以为是匹配整个单词结果只匹配到单个字符翻车翻得莫名其妙。区间写法0-9、a-z依赖 ASCII 码顺序[0-9]等价于任意数字字符。注意-只有在[]之间才是元字符写在[]外面就只是一个普通字符不需要转义。^在字符集合里做取非操作比如[^0-9]匹配任意非数字字符。笔记里给了一个组合应用匹配“以 abc 为开头并且最后一个字母不为数字”的字符串正则写成^abc[^0-9]。这里^在[]外面表示行首[^0-9]表示一个非数字字符。我拿 Python 验证过这个逻辑import re # 匹配以 abc 开头、第 4 个字符不是数字的字符串 pattern r^abc[^0-9] for s in [abcdnam, abc13, abc2]: print(s, bool(re.search(pattern, s)))这段代码里r^abc[^0-9]是原始字符串^锚定行首[^0-9]只消费一个非数字字符。运行结果abcdnam返回 Trueabc13和abc2返回 False。原因是abc13的第 4 个字符是1abc2的第 4 个字符是2都是数字被[^0-9]拒绝。如果你想让“后面整段都不能是数字”需要改成^abc[^0-9]*$或者用负向前查找这一点初学者最容易混淆。2.3 数字、字母、空白字符的元字符等价表直接用别重写笔记里把常用字符类别元字符整理得很全我直接做成速查表元字符说明等价写法\d数字字符[0-9]\D非数字字符[^0-9]\w字母、数字、下划线[a-zA-Z0-9_]\W对\w取非[^a-zA-Z0-9_]\s空白字符[\f\n\r\t\v]\S非空白字符[^\f\n\r\t\v]注意一个细节Python 3 的re模块里\w默认还会匹配 Unicode 字符比如中文、日文假名。如果你要严格匹配“纯 ASCII 的字母数字下划线”手写[a-zA-Z0-9_]比\w更稳。这在做英文 slug、变量名校验时是个很实际的区别。空白字符这块有个容易忽略的点Windows 文本的行尾是\r\nUnix/Linux 是\n。笔记里提到\r\n\r\n可以匹配 Windows 下的空白行因为它匹配两个连续的行尾标签正好是两条记录之间的空行。在跨平台处理文本时我习惯统一先把\r\n转成\n再跑正则否则^和$的边界行为会被\r干扰。提示\x0A匹配十六进制字符等价于\n\0匹配八进制。这类写法在二进制协议解析里偶尔用到日常文本处理基本用不上。3. 重复、边界与子表达式把邮箱、IP 和 HTML 标签三个案例拆到底3.1 重复量词的含义与贪懒切换、*、?、{m,n} 选型逻辑正则里真正拉开使用者差距的是重复匹配的量词。先把语义表放出来量词语义典型场景*匹配 0 个或多个可有可无的字符匹配 1 个或多个必须存在但数量不定?匹配 0 个或 1 个可选字符{n}匹配恰好 n 个固定长度字段{m,n}匹配 m 到 n 个长度范围{m,}至少 m 个最低数量要求*和默认是贪婪型会匹配尽可能多的内容。笔记里的例子很典型用a.c去匹配abcabcabc结果是整个字符串都被匹配而不是只匹配到第一段abc。原因就是.贪婪地吞掉了中间所有字符直到最后一个c才停下。解决办法是在量词后面加?转成懒惰型a.?c就会在遇到第一个c时停下来只匹配abc。这个知识点在做 HTML 解析、日志字段提取时几乎天天用到。我见过太多人写.*提取两个标记之间的内容结果把中间所有无关内容全带出来了问题不是正则写错而是没理解贪婪是默认行为。邮箱匹配是的经典应用。笔记里给的写法是[\w.]\w\.\w能匹配abc.defqq.com这种格式。注意[\w.]中.在字符集合内是普通字符所以它匹配字母数字、下划线和点后面的表示这些字符可以出现一次或多次。整个表达式的逻辑是用户名部分可以有多个字符和点之后是域名主体\.转义匹配域名分隔点最后是顶级域。这个正则不完美比如它会让abc..defqq.com通过但作为入门理解量词组合已经够用。3.2 边界匹配是位置不是字符\b 与 ^ $ 的常见用法单词边界\b匹配的是位置不是字符本身。它位于\w和\W之间可以理解为“单词的开始或结束”。所以\babc\b匹配出来的结果只有 3 个字符不包含任何空白或标点。与之对应的是\B匹配非单词边界的位置。位置匹配的另一个重点是^和$。^匹配整个字符串的开头$匹配结尾。注意在默认模式下^和$锚定的是整个文本不是每一行只有开启多行模式multiline后换行才会被当成边界。在 Python 里是re.MULTILINE在 JavaScript 里是m标志。笔记里有个应用匹配以//开始的注释行正则写成^\s*\/\/.*$。这里^锚定行首\s*允许行首有空格或 Tab\/\/匹配两个连续斜杠.*匹配注释内容到行尾。我第一次用这个正则时踩了坑忘记\/转义结果在 Python 里直接报错因为/不是正则元字符但也不是所有场景都能裸写。这个表达式在配合多行模式使用时能准确提取出整段代码里的注释行。提示\b只认 ASCII 的单词边界。在中文文本里\b经常不生效这个问题后面单独讲。3.3 子表达式与回溯引用IP 地址匹配是怎么一步步拼出来的子表达式用()定义它把一段模式当成一个独立元素。(ab){2,}匹配的是ab连续出现 2 次或更多次也就是能匹配ababab。子表达式可以嵌套但嵌套过深会变成“阅读灾难”我自己写过三层嵌套两周后回看都费劲。|是或元字符注意它的优先级比较低ab|cd表示要么ab要么cd不是a加b|cd的组合。想限制范围就用子表达式包起来比如(19|20)\d{2}匹配 1900-2099 的年份。回溯引用是()的高级用法\1引用第一个子表达式匹配到的具体内容。笔记里的 HTML 标题匹配是经典案例(h[1-6])\w*?\/\1拆开看(h[1-6])匹配h1到h6\w*?懒惰匹配标签文本\/\1中的\1回溯引用第一个子表达式的内容。也就是说正则匹配到h1x/h1是合法的因为\1此时是h1而h3x/h1无法匹配因为\1是h3但结尾写的是h1。这个“强制首尾标签一致”的能力是普通表达式做不到的。IP 地址匹配是子表达式嵌套的集大成者。IP 的每部分合法范围是 0-255需要分情况讨论一位数字、不以 0 开头的两位数字、以 1 开头的三位数、以 2 开头且第二位是 0-4 的三位数、以 25 开头且第三位是 0-5 的三位数。笔记里给的正则是((25[0-5]|(2[0-4]\d)|(1\d{2})|([1-9]\d)|(\d))\.){3}(25[0-5]|(2[0-4]\d)|(1\d{2})|([1-9]\d)|(\d))前面三组是相同的模式加\.分隔最后一组不带点。这个写法略冗余实际工程里更常见的简写是(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)用[1-9]?\d覆盖一位数和不以 0 开头的两位数。两种写法都能匹配192.168.0.1同时拒绝00.00.00.00和555.555.555.555。回溯引用还有个常见用途是做替换。笔记里的电话号码格式化案例查找正则(\d{3})(-)(\d{3})(-)(\d{4})把313-555-1234转成(313) 555-1234替换表达式写成($1) $3-$5。这里的$1、$3、$5分别引用第一、第三、第五个子表达式匹配的内容第二个子表达式(-)被故意丢弃。不同工具里回溯引用的替换语法有差异Python 是\1或\g1大部分编辑器是$1使用前先确认当前工具语法。3.4 前后查找与嵌入条件?、?、?(1) 的使用边界前后查找的特点是“只规定匹配内容的首尾条件但不消费这些内容”。向前查找用?比如\w(?)匹配abc.defqq.com中前面的abc.def匹配结果不包括本身。向后查找用?可以取到某些标记之后的内容。取非操作把换成!(?!)是负向前查找(?!)是负向后查找。笔记里确认了一个关键限制JavaScript 不支持向后查找Java 的支持也不完善。实际项目中我在 JavaScript 里遇到?需求时会改用捕获组[(])(\w)然后取第二组虽然麻烦但兼容性更好。这个“绕路方案”在旧版浏览器环境下尤其重要。嵌入条件更进一步它根据某个子表达式是否匹配来决定后续匹配。笔记里的括号匹配案例(\()?abc(?(1)\))解释一下(\()?匹配一个可选的左括号abc是必须匹配的文本(?(1)\))是条件表达式——如果子表达式 1 匹配了也就是文本里有左括号那么后面必须匹配一个右括号。所以它能匹配(abc)和abc但匹配不了(abc这种残缺写法。另一个案例是匹配美国 ZIP4 邮编格式\d{5}(?(?-)-\d{4})。当五位数后面跟了-就要求继续匹配四位数字否则只匹配五位数字。这种语法在 PCRE 中可用但在 JavaScript 和 Python 的re模块里并不支持。我的建议是条件嵌入这类高级特性先确认目标引擎支持不要直接抄到项目里。4. 正则排查与常见问题五个我踩过的坑和对应解法4.1 贪婪匹配把整段文本吞掉了现象用正则提取两个 HTML 标签之间的内容写了h1.*/h1结果从第一个h1一直匹配到文本末尾的/h1中间所有标签全部被吞进来根本不是想要的那一小段。原因*和默认贪婪会匹配尽可能多的内容直到最后一个可能的结束位置才停止。文本越长吞掉的内容越多。解决改成懒惰写法h1.*?/h1让匹配在遇到第一个/h1时立即停止。如果还要保证首尾标签一致用回溯引用h([1-6]).*?\/\1。从那以后我写任何.*都会先问自己一句这里到底该不该懒惰默认贪婪这个特性熟悉之后是好事不熟悉就是定时炸弹。4.2 同一个 ^在 [] 内外是两种意思现象想匹配“不以 abc 开头的行”写了^[^abc]结果发现它匹配的是“以 a、b、c 之外的任意字符开头的行”和预期完全不是一回事。原因^在[]内表示取非在[]外表示行首。[^abc]是“匹配任意一个不是 a、b、c 的字符”不是“排除单词 abc”。这个语义差异初学者特别容易踩。解决先判断^所处的位置。排除单个字符用[^...]排除一段字符串用负向前查找^(?!abc)。(?!abc)表示“接下来的字符不能是 abc”配合^就是“不以 abc 开头”。注意负向前查找不是所有工具都支持主流的 Python、JavaScript、Java 都支持但一些老旧的文本编辑器可能不认。4.3 匹配 IP 地址用了 \d{1,3}非法地址照样过现象校验 IP 地址时图省事写了\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}结果999.999.999.999这种彻底非法的地址也被匹配了。原因{1,3}只限制了数字的位数没限制数值范围。正则默认按“字符串模式”匹配不理解数字的数学大小。解决按 0-255 的规则分段匹配正确的简化写法是ip_pattern r^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$注意分支顺序很重要25[0-5]必须放在2[0-4]\d前面否则255会被2[0-4]\d吃掉前两位25然后第三位5匹配\d整个匹配逻辑就乱了。分支顺序是正则性能与正确性的双重关键点。4.4 同一个正则换个语言引擎就不认了现象在 JavaScript 调试器里跑得好好的正则搬到 Python 直接报错或者在 Python 里正常放到 Java 的Pattern.compile里抛异常。原因正则不是一门统一语言不同引擎支持的语法子集有差异。JavaScript 不支持向后查找(?)Python 的re模块不支持条件嵌入(?(1)...)Java 对命名分组和 Unicode 属性的支持与 Python 也不完全一致。解决写正则之前先确认目标语言和工具支持哪些特性。能用基础语法解决的需求尽量不要上高级特性。我现在的习惯是核心表达式先在最简单的语法层面实现然后针对目标语言做最小化适配而不是写一份“豪华版”正则到处拷。4.5 中文文本里 \b 边界匹配失效现象在中文文档里用\b项目\b想匹配独立的“项目”一词结果一个都匹配不到。原因\b的定义是\w和\W之间的位置。在 Python 3 的re模块里\w默认匹配 Unicode 字符所以汉字也属于\w在 JavaScript 里\b按 ASCII 处理中文直接不算\w也不算\W。两种引擎下汉字两侧都不存在“从单词字符到非单词字符”的边界。解决改用显式边界比如用前后查找规定“项目”前后的字符必须是开头、空白或标点(?^|[。\s])项目(?[。\s]|$)在支持向后查找的引擎里这个写法有效JavaScript 不支持?就改用捕获组提取或者用(^|[。\s])项目然后在结果里手动去掉首字符。中文文本的正则处理边界问题是最容易被忽略的。5. 验证正则的实用技巧把笔记里的表达式搬到命令行和脚本里笔记看得再多不动手验证等于没看。我有三个固定习惯每次整理正则知识或写新的匹配表达式时都会走一遍。第一用 Python 写最小回归脚本。正则改一次跑一次用真实文本和反例文本同时验证import re # 验证 IP 地址同时测合法与非法样本 pattern r^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$ test_cases [192.168.0.1, 255.255.255.255, 00.00.00.00, 555.555.555.555, 256.1.1.1] for ip in test_cases: print(f{ip}: {bool(re.match(pattern, ip))})这段代码里re.match从字符串开头匹配配合^...$确保全串匹配。每个测试用例都分“合法样本”和“非法样本”两组合法样本必须全部为 True非法样本必须全部为 False。只测一组正样本的验证都不是好验证正则表达式对“不该匹配的内容”的拒绝能力往往比“能匹配的内容”更重要。第二在真实文件上用 grep 验证。正则不是只在 Python 里有用命令行里同样通用。比如验证注释行匹配grep -nE ^[[:space:]]*// demo.c-n输出行号-E启用扩展正则POSIX 字符类[[:space:]] 匹配空格和 Tab。退出码 0 表示有匹配1 表示没有匹配这个退出码本身就能帮你快速判断表达式在真实数据上是否成立。爬虫场景里提取链接、清洗文本我也习惯先用 grep 在样本 HTML 上试一遍再进 Python 脚本。第三把常用表达式沉淀成速查清单。笔记里的邮箱、IP、注释行、电话号码案例按“我要干什么 → 正则怎么写”压缩成一张表放在自己能随手翻到的地方目标正则匹配邮箱[\w.]\w\.\w匹配 IPv4 地址^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$匹配代码注释行^\s*//.*匹配 HTML 首尾一致标签h([1-6]).*?\/\1第一次正式用正则做批量数据清洗时我图省事写了\d{1,3}去匹配 IP结果把一批假地址直接写进了报表被数据方追着问了一下午。从那以后我每次写完正则都强制自己走一遍“正样本 反样本 真实文件”的验证流程再落到正式脚本里。这份 PDF 笔记把知识点压缩得很密集适合快速过一遍建立框架也适合遇到问题时翻回来查细节。希望帮到你。本文还有配套的精品资源点击获取