负向预查(?!pattern)是在当前位置断言后续不能紧接pattern,不匹配字符也不移动位置;必须配合^$锚定,禁用.*等变长表达式,排除多前缀用(?!ss|ff|pp),web路由中/^\/admin\/(?!auth\/).*$/可精准放行非认证路径。

负向预查的语法是 (?!pattern)
它不是“排除某段文本”,而是“在当前位置,断言后面**不能紧跟着** pattern”。这个断言本身不匹配任何字符,也不移动匹配位置——只做真假判断。写错位置或漏锚点,结果就完全不对。
常见错误写法:(?!admin)w+ —— 它会在每个位置都尝试:比如字符串 adminUser 中,第一个 a 后面是 dminUser,不等于 admin,所以断言通过,w+ 就直接吞掉整个字符串。
正确做法必须加锚定:^(?!admin).*$,确保“整行开头之后不能是 admin”。
-
^和$是关键边界,否则断言作用范围失控 -
(?!...)里不能用变长表达式(如.*auth),PCRE 不允许 - 若要排除多个前缀,用竖线分隔:
(?!SS|FF|PP) - 注意结尾斜杠:匹配
/admin/auth/时,(?!auth/)才能精确否定子目录,(?!auth)会误杀/admin/authz
匹配 /admin 开头但排除 /admin/auth/ 的完整正则
典型场景:Web 路由中间件中放行所有 /admin/xxx,但拦截认证路径。正确写法是:
^/admin/(?!auth/).*$
拆解说明:
-
^/admin/:严格匹配字面量/admin/,末尾斜杠防止匹配到/administer -
(?!auth/):在/admin/后立即检查,接下来两个字符不能是a和u(即不能是auth/) -
.*$:匹配后续任意内容(包括空字符串,即/admin/本身也合法)
如果漏掉 / 写成 (?!auth),那么 /admin/authz 会被错误排除(因为 authz 开头是 auth);而加上 / 后,authz 不再满足条件,断言通过。
多行文本中排除特定前缀行
处理日志或报表时,常需跳过以 SS、FF 等开头的整行,再提取数值。PHP 中要用 /m 修饰符启用多行模式,并把断言放在行首:
/^(?!SS|FF|PP).*(\d{1,2}[,.]\d{1,2})$/m
要点:
-
/m让^和$分别匹配每行开头和结尾,而非整个字符串首尾 -
(?!SS|FF|PP)必须紧跟^,否则可能落在行中任意位置,失去“行首排除”语义 - 捕获组
(\d{1,2}[,.]\d{1,2})放在断言之后,确保它匹配的是被允许行里的内容 - 不要写成
^(?!.*SS)——.*在断言里是非法的(PCRE 不支持)
容易忽略的兼容性细节
PHP 默认使用 PCRE 库,不支持负向后行断言 (? 中的变长模式(比如 <code>(? 会报错)。很多初学者想“从后面排除”,结果写出语法无效的正则。
真正需要“排除某后缀”时(比如文件名不能以 .log 结尾),得把否定逻辑提前到扩展名之前:([^s\/]+)(?!.log|.tmp).txt,而不是试图在 .txt 后面加断言。
断言永远只看“当前位置之后紧邻的内容”。差一个 ^、少一个 /、括号嵌套错一层,匹配行为就彻底偏移——这不是写得不够复杂,而是没想清楚“你在哪个位置做判断”。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











