正则表达式:如何排除多个字符组?

16

我有一组网址:

/products

/categories

/customers

现在假设有一个名叫约翰的顾客,我想为他提供一个更短的网址让他能够方便地访问自己的账户页面:

before : /customers/john
after  : /john

(假设客户名称都是唯一的)

我正在尝试找出一个合适的正则表达式分发器,以便所有客户都可以使用此功能:

/marry
/james
/tony-the-red-beard

这是我现在得到的(使用PHP):

'/^\/([^(products|categories|admin)].+)$/' => /customers/$1

这似乎不起作用。有人可以帮我吗?

3个回答

27

你需要的是一个负向先行断言assertion。你想要表达的是“我想匹配除了这些特定字符串之外的任何字符串”。正则表达式中的断言可以针对一个字符串进行匹配,但它不会消耗任何字符,从而使这些字符可以被你的正则表达式的其余部分匹配。你可以通过在模式中包裹(?!)来指定一个负面断言。

'/^\/(?!products|categories|admin)(.+)$/'

请注意,如果您不允许客户名称包含斜杠,则可能需要使用以下内容:
'/^\/(?!products|categories|admin)([^/]+)$/'

11

这完全是解决问题的错误方式,但是可以在不使用负向先行断言的情况下表达固定的负向先行条件。

^ (
( $ | [^/] |
  / ( $ | [^pc] |
    p ( $ | [^r] |
      r ( $ | [^o] |
        o ( $ | [^d] |
          d ( $ | [^u] |
            u ( $ | [^c] |
              c ( $ | [^t] |
                t ( $ | [^s] ))))))) |
    c ( $ | [^au] |
      a ( $ | [^t] |
        t ( $ | [^e] |
          e ( $ | [^g] |
            g ( $ | [^o] |
              o ( $ | [^r] |
                r ( $ | [^i] |
                  i ( $ | [^e] |
                    e ( $ | [^s] )))))))) |
      u ( $ | [^s] |
        s ( $ | [^t] |
          t ( $ | [^o] |
            o ( $ | [^m] |
              m ( $ | [^e] |
                e ( $ | [^r] |
                  r ( $ | [^s] ))))))))))
) .* ) $

5
哇,我不确定自己是应该感到印象深刻还是困扰。是的,在正则表达式中可能会使用负向先行断言,即使没有语法糖也可以做到,就像可以匹配整个字符范围而不使用字符类一样。我不确定为什么你想要写 (a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z)而不是[a-zA-Z],同样地,我不确定为什么你想要避免负向先行断言 (?!) - Brian Campbell
不,我只是有点害怕。我相信会有更好的解决方案。而且这并不是世界末日。 - Shawn
7
只是为了好玩而已,因为其他人已经发布了正确的答案。 - ephemient
3
信不信由你,有些正则表达式引擎不支持前瞻。对于这些引擎,这是唯一的方法。看起来很吓人,但如果其他方法都失败了……加1。 - Tomalak

0
你正在错误地使用否定字符类。否定字符类表示“不匹配包含的字符”。你想要表达的是“如果我在这里指定的内容存在,则不匹配”。为了做到这一点,你需要更有创意。可能需要一些负向回顾。我不确定php的正则表达式引擎,但类似于这样的东西应该可以工作。
/^\/(?<!(?:products|categories|admin))(.+)$/

所以,负回顾后断言 (?<! ... ) 表示如果在其前面有 productscategoriesadmin,则不匹配 .+。这个规则被放到一个非捕获组中 (?: ... )

请参考正则表达式高级语法参考获取额外的帮助。


嗯,您不需要像回溯那样复杂(实际上,我认为那样做行不通,因为您尚未消耗这些字符),并且负断言始终是非捕获的,因为它们是负面的;它们会捕获什么呢? - Brian Campbell
感谢Brian的提示,这里是可能修复您语句的方法: /^/(.+)(?<!/(?:products|categories|admin))$/ 这意味着“匹配'/anything',但不在/products、/categories、/admin之后。如果我错了,请纠正我。 - Shawn
@Qberticus:使用断言会更直观,但效果相同。 - Shawn
我总是包含非捕获组的,无论如何。这是明确的,并且是为了以防万一有什么变化。回顾向前并不花哨(?! 和 (?<! 在这个正则表达式中是语义上的区别)。一个表示如果后面跟着/就不匹配,另一个表示如果前面有.+就不匹配。不过,我想知道在php引擎中哪个更快,可能是向前查找。 - Rich Schuler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接