在一个可选字符串上进行非贪婪匹配,另一个字符串上进行贪婪匹配的正则表达式匹配问题。

7

我已经进行了一段时间的调研,但没有找到匹配以下模式的线索(虽然我对正则表达式非常陌生),它看起来像

/abc/foo/bar(/*) 

或者
/abc/foo/bar/stop

我希望将上述字符串匹配并捕获为/abc/foo/bar。现在,"/stop"是一个可选的字符串,可能附加在模式的末尾。目标是在忽略“stop”(如果存在,并且如果“stop”出现多次,则停止在第一个“stop”)的情况下获取所需的捕获,同时允许在中间使用尽可能多的斜杠,除了行末的斜杠。
如果我只是简单地这样做:
^(/.*[^/])/*$

我需要一份能够涵盖所有斜杠的贪婪正则表达式,直到我去除可能的最后一个斜杠。但是为了接受第二种情况,即我有一个可选的"/stop",我需要以非贪婪的方式匹配,直到找到第一个可能的"/stop"并在那里停止。

我该如何制作一个单一的正则表达式来匹配这两种情况?

编辑:不确定我的先前示例是否足够清晰。请尝试给出更多,例如我想在以下所有字符串中匹配并捕获"/abc/foo/bar":

/abc/foo/bar
/abc/foo/bar/
/abc/foo/bar///
/abc/foo/bar/stop
/abc/foo/bar/stop/foo/bar/stop/stop
/abc/foo/bar//stop

虽然它不会匹配以下任何一个:

/abc/foo/bar/sto (will match the whole "/abc/foo/bar/sto" instead)
/abc/foo/bar/abc/foo/bar (it will catch "/abc/foo/bar/abc/foo/bar" instead)

如果这样讲得够清楚,请告诉我。谢谢!


1
你能给一些例子吗? - JonatasTeixeira
3
请标记您正在使用的正则表达式语言。 - zzzzBov
顺便提一下,我建议不要试图在一个正则表达式中做太多事情。正则表达式的目的是通过更简洁地匹配复杂模式来使您的代码更易于理解。可以使用两个或三个简单的正则表达式来解决此问题,这样您的代码会更好。 - zzzzBov
@zzzzBov 我已将pcre添加为标签,但我不介意人们提供适用于所有语言的通用解决方案 :-) 谢谢您指出结合简单正则表达式的要点,这很有道理。 - Superziyi
2个回答

4

试试这个:

/^(?:\/+(?!$|(?:stop\/?))[^\/]+)*/

Regex101演示

解释:

这个正则表达式匹配字符串的开头 (^),后跟零个或多个以下模式:

  • 一个或多个斜杠 (\/+),后面没有字符串结尾 ($) 或者有 stop。然后是
  • 一个或多个非斜杠字符 ([^\/]+)

正则表达式可视化

这里有一个Debuggex演示带有可工作的单元测试。

编辑:这里有一个替代,可以说更简单的正则表达式:

/^.+?(?=\/*$|\/+stop\b)/

这将以非贪婪的方式匹配一个或多个字符,然后在匹配后面的内容是以下情况之一时停止:

  1. 字符串结尾($),可能在前面有一个或多个斜杠(\/*
  2. 一个或多个斜杠,单词“stop”和单词边界。

这里有一个Regex101演示这个选项.

编辑 2:如果您想测试此代码,请使用以下简单的JavaScript测试对第二个正则表达式进行测试,并将结果记录到控制台中:

var re = /^.+?(?=\/*$|\/+stop\b)/,
    test_strings = ["/abc/foo/bar",
                    "/abc/foo/bar/",
                    "/abc/foo/bar///",
                    "/abc/foo/bar/stop",
                    "/abc/foo/bar/stop/foo/bar/stop/stop",
                    "/abc/foo/bar//stop",
                    "/abc/foo/bar/sto",
                    "/abc/foo/bar/abc/foo/bar"];
for(var s = 0; s < test_strings.length; s++) {
    console.log(test_strings[s].match(re)[0]);
}

/*
Results:

/abc/foo/bar
/abc/foo/bar
/abc/foo/bar
/abc/foo/bar
/abc/foo/bar
/abc/foo/bar
/abc/foo/bar/sto
/abc/foo/bar/abc/foo/bar 

*/

在这种情况下,它将匹配/abc/foo/bar$演示 - elixenide
非常感谢您的帮助。由于我现在还是新手,所以我也理解了那些前瞻性的内容。我喜欢方案2,因为它真的简单得多。我可以看到诀窍是将其中一个贪婪情况转换为懒惰情况。懒惰匹配的性能比贪婪匹配差很多吗?而且我想要“匹配和捕获”两者,所以通过在".+?"周围添加一对括号即可完成任务。虽然对于第一个,我实际上无法正确捕获,即使是^((/+(?!$|(?:stop/?))[^/]+)*)并只取$1,在该演示中仍然不能准确显示(它会将前两个字符串组合在一起) - Superziyi

2
你可以尝试像这样做:

你可以尝试像这样做:

^((?:/[^/]+)+?)(?:/+|/+stop(?:/.*)?)$

演示

如果有原子组可用,最好写成:

^((?:/[^/]+)+?)(?>/+$|/+stop(?:/.*)?)

演示

如果有lookahead:

^/(?>[^/]+|/(?!/*(?:$|stop(?:/|$))))+

演示

ps:如果您的定界符是斜杠,请不要忘记转义斜杠。

正如Ed Cottrell所指出的,像Javascript或Python的re模块这样的语言中不支持原子分组等功能。然而,可以使用一个先行断言自然上就是原子的事实来有效地模拟此功能:(?>a+) <=> (?=(a+))\1


比较一下这个演示,它不匹配 /stop 或尾部斜杠:http://regex101.com/r/fH7tY0/2 - elixenide
非 PCRE 指的是任何实现正则表达式与 PCRE 库 不同的东西。例如,PCRE 支持 正向和反向查找断言以及回溯控制,如 (*SKIP)(*FAIL)。类似于 PCRE 但不完全符合规范的正则表达式风格并不总是支持这些功能。例如,JavaScript 和 VBScript 都不支持我刚列出的这些特性。 - elixenide
澄清一下,Perl、PHP和许多其他正则表达式的变体都可以很好地支持你提供的这三个例子。但是其他一些语言可能不行。在我看来,与正则表达式相关的问题的答案应尽可能具有可移植性,特别是当未指定将使用正则表达式的语言时。 - elixenide
@EdCottrell:澄清一点,在我的回合中,整个第一个列表都支持这三种模式,而所有这些语言都不使用PCRE引擎。每次我们编写一个模式时,我们需要为可移植性设置限制。在当前情况下,没有限制!在我最糟糕的噩梦中,OP可能正在使用vim语法或他自己编写的正则表达式引擎。所有这些都是为了说明可移植性是一种幻想。 - Casimir et Hippolyte
@Superziyi:没问题,我认为这个论坛提供了一个机会来驳斥我的答案是一件好事。欢迎Ed Cottrell。 - Casimir et Hippolyte
显示剩余7条评论

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