正则表达式 - 获取两个单词之间不包含某个单词的字符串

7

我一直在寻找解决方法,但无法实现。我并不是完全的新手。

我需要获取由(包括)START和END分隔的文本,该文本不包含START。基本上,我找不到一种不使用高级功能来否定整个单词的方法。

示例字符串:

abcSTARTabcSTARTabcENDabc

期望的结果:

STARTabcEND

不好的结果:

STARTabcSTARTabcEND

我不能使用向后搜索的功能。我正在这里测试我的正则表达式:www.regextester.com

感谢任何建议。


如果文本是abcSTARTabcENDabcSTARTabcENDabc,你想要匹配两个吗? - Tim Pietzcker
没想到那个...不过,如果需要的话我可以找到第二个匹配。 - rrr
最好用一个正则表达式完成。我已经添加了答案。 - Tim Pietzcker
你可以在rubular.com上测试你的正则表达式。 - Jigish Chawda
5个回答

9

试一下这个

START(?!.*START).*?END

Regexr网站在线查看它。

(?!.*START)是一个负向先行断言。它确保单词“START”没有跟在后面。

.*?是非贪婪匹配,匹配所有字符直到下一个“END”,因为负向先行断言只是向前查找而不捕获任何内容(零长度断言)。

更新:

我再考虑了一下,上面的解决方案是匹配到第一个“END”。如果不想这样做(因为你要从内容中排除START),请使用贪婪版本。

START(?!.*START).*END

这将匹配到最后一个"END"。


对于好的回答并简单解释所有运算符给予+1。 - shelleybutterfly
2
如果字符串中有多个START...END对,这将失败。 (更准确地说,它只会在字符串中找到最后一个START...END对。) - Tim Pietzcker
2
澄清Tim的评论:如果存在“START”的任何第二次出现,无论是在END之前还是之后(例如abcSTARTabcENDxyzSTART),您的正则表达式将不会匹配到您期望的位置。 - vladr
是的,它只是询问未来是否有任何“开始”出现,如果有,就不会匹配。这不是所需的(描述)行为。 - AturSams

7
START(?:(?!START).)*END

这段代码将适用于任意数量的 START...END 成对出现。以下是Python演示:

>>> import re
>>> a = "abcSTARTdefENDghiSTARTjlkENDopqSTARTrstSTARTuvwENDxyz"
>>> re.findall(r"START(?:(?!START).)*END", a)
['STARTdefEND', 'STARTjlkEND', 'STARTuvwEND']

如果你只关心STARTEND之间的内容,可以使用以下方法:
(?<=START)(?:(?!START).)*(?=END)

看这里:

>>> re.findall(r"(?<=START)(?:(?!START).)*(?=END)", a)
['def', 'jlk', 'uvw']

可以的,这个就可以了。+1(虽然你可能想要提及/使用s点匹配所有标志)。 - ridgerunner

4
非常平凡的解决方案是START(([^S] | S * S [^ ST] | ST [^ A] | STA [^ R] | STAR [^ T]) *(S(T(AR?)?)?)?) END 。现代正则表达式有负断言,可以更优雅地实现此目的,但我理解您关于“向后搜索”的评论可能意味着您不能或不想使用此功能。
更新:仅为完整起见,请注意,上述对于结束定界符来说是贪婪的。要仅捕获最短的字符串,请将否定扩展到包括结束定界符 - START(([^ ES] | E * E [^ ENS] | EN [^ DS] | S * S [^ STE] | ST [^ AE] | STA [^ RE] | STAR [^ TE])*(S(T(AR?)?)?| EN?)?)END 。这在大多数文化中都会超过折磨阈值。
错误修复:此答案的早期版本存在错误,即SSTART可以成为匹配的一部分(第二个S将匹配 [^ T] 等)。我通过在 [^ ST] 中添加S 并在非可选的 S 之前添加 S * 来修复了此问题,以允许任意重复 S

最后一部分是干什么用的?为什么你需要(S(T(AR?)?)?)? - AturSams
好的!我明白了...你需要 ...(S(T(AR?)?)?)?...,否则,你必须在 SSTSTASTAR 之后消耗字符...这太聪明了。 - AturSams
不确定您的意思。在END分隔符之前允许使用START的子字符串,我们一直在防止这些子字符串匹配。 - tripleee
我不理解这个答案。我的问题是为什么你需要有(S(T(AR?)?)?)?这部分,但我认为原因是否则你无法匹配像STARTSTAREND这样的东西。 (S(T(AR?)?)?)?让您干净地消耗任何直接出现在END之前的STAR子字符串。 - AturSams
是的,没错。在比赛早期,如果STAR后面跟着的不是T,我们允许它后面跟着某些东西,但就在结束分隔符之前,我们也允许它后面什么都没有。(在这种情况下使用“consume”有点奇怪,个人认为。) - tripleee
显示剩余3条评论

3
我可以建议在Tim Pietzcker的解决方案上进行改进吗?我认为使用START(?:(?!START).)*?END会更好,只会捕获一个紧随其后没有任何STARTEND之间的STARTEND。 我正在使用.NET,而Tim的解决方案也会匹配类似START END END的表达式。 至少在我的个人情况下,这是不希望发生的。

0

[编辑:我留下了这篇文章,以获取有关捕获组的信息,但我给出的主要解决方案是不正确的。 (?:START)((?:[^S]|S[^T]|ST[^A]|STA[^R]|STAR[^T])*)(?:END) 正如评论中指出的那样,将无法工作;我忘记了被忽略的字符不能被删除,因此您需要像...|STA(?![^R])|这样的东西仍然允许该字符成为END的一部分,因此在类似STARTSTAEND的情况下失败;因此这显然是更好的选择;以下应显示使用捕获组的正确方法...

使用“零宽度负向先行断言”运算符“?”给出的答案,带有捕获组:(?:START)((?!.*START).*)(?:END),它使用$1捕获内部文本进行替换。如果您想捕获START和END标记,可以执行(START)((?!.*START).*)(END),其中$1=START $2=text,$3=END或通过添加/删除()?:来进行各种其他排列。

这样,如果您要使用它进行搜索和替换,您可以执行类似于BEGIN$1FINISH的操作。因此,如果您从以下内容开始:

abcSTARTdefSTARTghiENDjkl

您将获得ghi作为捕获组1,并且使用BEGIN$1FINISH进行替换将给您以下结果:

abcSTARTdefBEGINghiFINISHjkl

这将允许您仅在正确配对时更改START/END标记。

每个(x)都是一个组,但我已经为除中间组之外的每个组放置了(?:x),以将其标记为非捕获组;我唯一没有使用?:的是中间组;但是,如果您想移动它们或其他操作,也可以捕获BEGIN/END标记。

有关Java正则表达式的完整详细信息,请参见Java regex documentation


您在模式STARTSTAEND上失败了。 - tripleee
@tripleee 叹气,是的,确实我需要忽略那些带有?!字符的内容,这有点违背了初衷。感谢您指出这一点。 - shelleybutterfly

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