正则表达式的可变长度lookbehind-assertion替代方案

Posted

技术标签:

【中文标题】正则表达式的可变长度lookbehind-assertion替代方案【英文标题】:Variable-length lookbehind-assertion alternatives for regular expressions 【发布时间】:2012-07-23 08:04:48 【问题描述】:

在 Python/php/javascript 中是否有支持变长lookbehind-assertion的正则表达式实现?

/(?<!foo.*)bar/

如何编写具有相同含义但不使用lookbehind-assertion的正则表达式?

这种类型的断言是否有可能有一天会实现?

事情比我想象的要好得多。

更新:

(1) 已经有正则表达式实现支持变长lookbehind-assertion。

Python 模块 regex(不是标准的 re,而是附加的 regex 模块)支持此类断言(并具有许多其他很酷的功能)。

>>> import regex
>>> m = regex.search('(?<!foo.*)bar', 'f00bar')
>>> print m.group()
bar
>>> m = regex.search('(?<!foo.*)bar', 'foobar')
>>> print m
None

正则表达式中有一些东西是 Perl 做不到而 Python 可以做到的,这对我来说是一个非常大的惊喜。也许,Perl 也有“增强的正则表达式”实现?

(感谢并为 MRAB +1)。

(2) 现代正则表达式中有一个很酷的功能\K

这个符号意味着当你进行替换时(在我看来,断言最有趣的用例是替换),在\K 之前找到的所有字符都不能更改。

s/unchanged-part\Kchanged-part/new-part/x

这几乎就像一个后视断言,但当然不是那么灵活。

更多关于\K

Perl Regular Expression \K Trick PCRE Regex Spotlight: \K

据我了解,您不能在同一个正则表达式中使用 \K 两次。而且你不能说直到什么时候你想“杀死”你找到的角色。一直到行首。

(感谢并 +1 ikegami)。

我的其他问题:

能不能说\K效果的终点一定是哪一点? Perl/Ruby/JavaScript/PHP 的增强正则表达式实现怎么样?类似于 regex 对于 Python 的东西。

【问题讨论】:

要知道如何正确地编写一个不使用后向断言的替代方案,我们需要更多的上下文。这实际上是为了什么? @minitech:没有额外的上下文。这是一个普遍的问题 不,它需要额外的上下文。目前解决你的问题最好的方法是使用indexOf找到'foo',然后重复查找所有'bar' @minitech:我可以去掉这个简单的例子;我提供它只是为了说明目的;问题是:“我如何(通常)避免后视否定断言以及(通常)我可以使用什么?”。为什么不喜欢ikegami的回答?我认为答案几乎是完美的。我不知道这个\K 把戏,我发现它真的很杀 @ikegami:“这是你从哪一端开始匹配的问题”,好的,我明白了。我认为这只是一个定义问题。 【参考方案1】:

大多数情况下,您可以通过使用\K 来避免可变长度的lookbehinds。

s/(?<=foo.*)bar/moo/s;

s/foo.*\Kbar/moo/s;

最后遇到的\K 之前的任何内容都不会被视为匹配的一部分(例如,出于替换目的,$&amp; 等)

否定的lookbehinds有点棘手。

s/(?<!foo.*)bar/moo/s;

s/^(?:(?!foo).)*\Kbar/moo/s;

因为(?:(?!STRING).)* 对应于STRING,就像[^CHAR]* 对应于CHAR


如果你只是匹配,你甚至可能不需要\K

/foo.*bar/s

/^(?:(?!foo).)*bar/s

【讨论】:

\K 这个技巧真的很酷,但是可以在一个正则表达式中指定多个\K 吗?可能,不是 否(或没用),但您可以使用捕获:s/foo.*\Kbar/moo/s; === s/(foo.*)bar/$1moo/s; 捕获很明显,但并不有趣:) \K 要好得多:) 但是你只能拥有一个。我是在指出如果您要求的不止一个,您可以做什么。 (在 5.10 之前引入 \K 时,捕获也可以工作。) 这太好了,非常感谢。但请添加关于 \K 实际含义的注释。这对 Google 来说并不容易。【参考方案2】:

对于 Python,有一个支持可变长度后视的正则表达式实现:

http://pypi.python.org/pypi/regex

它被设计为向后兼容标准 re 模块。

【讨论】:

谢谢!这确实有效,并且该模块通常非常有趣。非常感谢! +1 此答案已添加到 Stack Overflow Regular Expression FAQ 的“Lookarounds”下。 Python 3.4.1 上流畅运行。它似乎也比re快一点。【参考方案3】:

您可以反转字符串和模式并使用可变长度前瞻

(rab(?!\w*oof)\w*)

粗体匹配:

raboof rab7790oof raboo rabof rab rabo raboooof rabo

据我所知的原始解决方案:

Jeff 'japhy' Pinyan

【讨论】:

Benjamin,谢谢你的回答,但你确定可以反转任何模式吗? 我从来没有遇到过这种方法不起作用的情况。创建模式比“正常”模式需要更多时间。 此答案已添加到 Stack Overflow Regular Expression FAQ 的“Lookarounds”下。【参考方案4】:

您显示的正则表达式将找到bar 的任何实例,not 前面有foo

一个简单的替代方法是首先将foo 与字符串匹配,然后找到第一次出现的索引。然后搜索bar,看看你是否能找到出现在该索引之前的事件。

如果你想找到bar 的实例,而不是直接 前面有foo,我也可以为此提供一个正则表达式(不使用lookbehind),但它会非常难看.基本上,颠倒/foo/ 的含义——即/[^f]oo|[^o]o|[^o]|$/

【讨论】:

Alex,谢谢你的回答,但总的来说一切都不像你写的那么简单。我只提供了一个带有断言的正则表达式的小例子。当然,re 可能要复杂得多,并且断言可能在其中的深处。在这种情况下,您不能简单地检查字符串中的某个子字符串。 Alex,当你需要“bar 的实例不直接以foo 开头”时,你可以使用普通的lookbehind assertion (?&lt;!foo)bar。这样可行。但诀窍在于 foo 和 bar 之间可以是其他字符。【参考方案5】:
foo.*|(bar)

如果foo首先在字符串中,则正则表达式将匹配,但不会有组。

否则,它会找到bar并将其分配给一个组。

因此您可以使用此正则表达式并在找到的组中查找结果:

>>> import re
>>> m = re.search('foo.*|(bar)', 'f00bar')
>>> if m: print(m.group(1))
bar
>>> m = re.search('foo.*|(bar)', 'foobar')
>>> if m: print(m.group(1))
None
>>> m = re.search('foo.*|(bar)', 'fobas')
>>> if m: print(m.group(1))
>>> 

Source.

【讨论】:

以上是关于正则表达式的可变长度lookbehind-assertion替代方案的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式的可变长度lookbehind-assertion替代方案

StringBuffte和正则表达式

通过 db2-luw 上的 xmlquery 使用正则表达式执行更新语句时出现 SQL 错误

如何使用正则表达式删除括号内的文本?

您如何使用正则表达式“量化”可变数量的行?

javascript中字幕的正则表达式中的可变行数