这两个回溯控制动词仅在Perl、PCRE和pypi正则表达式模块中实现。
(*SKIP)(*FAIL)
技巧的思想是消耗您想要避免的字符,这些字符不能成为匹配结果的一部分。
使用此技巧的经典模式如下所示:
What_I_want_to_avoid(*SKIP)(*FAIL)|What_I_want_to_match
正则表达式引擎会按照以下方式处理字符串:
默认情况下,模式的第一个标记在从左到右的每个字符上进行测试(大多数时候是这样,但某些正则表达式引擎可以设置为从右到左工作,如果我没记错的话.net可以做到这一点)
如果第一个标记匹配,则正则表达式引擎将使用下一个字符测试模式的下一个标记(在第一个标记匹配之后)等等。
当标记失败时,正则表达式引擎将获取由最后一个标记匹配的字符,并尝试另一种方法使模式成功(如果也不起作用,则正则表达式引擎会对先前的标记执行相同的操作等等)
当正则表达式引擎遇到(*SKIP)
动词时(在这种情况下,所有以前的标记显然都已成功),它不再有权回到左侧的所有先前标记,并且不再有权重试所有匹配的字符以使用模式的另一个分支或在字符串中的下一个位置,直到(*SKIP)
动词右侧的模式失败时,最后匹配的字符(包括)。
(*FAIL)
的作用是强制模式失败。因此,所有在(*SKIP)
左侧匹配的字符都将被跳过,并且正则表达式引擎将在这些字符之后继续其工作。
示例模式仅在第一个分支在(*SKIP)
之前失败以允许测试第二个分支时才能成功。
您可以在这里找到另一种解释方式。
关于Java和其他没有这两个特点的正则表达式引擎
回溯控制动词在其他正则表达式引擎中未实现,也没有等价物。
但是,您可以使用几种方法来做同样的事情(更明确地说,避免可能由模式的其他部分匹配的内容)。
使用捕获组:
方法1:
What_I_want_to_avoid|(What_I_want_to_match)
你只需要提取第一组捕获组(或测试它是否存在),因为这是你要查找的内容。如果您使用该模式执行替换,则可以使用匹配结果的属性(偏移量、长度、捕获组)使用字符串函数进行替换。其他像JavaScript、Ruby这样的语言允许使用回调函数作为替换。
方法2:
((?>To_avoid|Other_things_that_can_be_before_what_i_want)*)(What_I_want)
这是一种更简便的替换方式,无需回调函数,替换字符串只需要以\1
(或者$1
)开始即可。
使用lookaround:
例如,您想要查找一个单词,该单词不嵌套在两个其他单词之间 (假设这两个单词为S_word
和 E_word
, 且这两个单词不同(请参见Qtax评论))。
(在本示例中,允许出现边缘情况S_word E_word word E_word
和S_word word S_word E_word
。)
采用回溯控制语句的方法如下:
S_word not_S_word_or_E_word E_word(*SKIP)(*F)|word
为了使用这种方式,正则表达式引擎需要在一定程度上允许可变长度的反向断言。对于 .NET 或新的正则表达式模块,没有问题,反向断言可以具有完全可变的长度。在Java中也是可能的,但必须限制大小 (例如:(?<=.{1,1000})
)。
Java 的等效写法为:
word(?:(?!not_S_word_or_E_word E_word)|(?<!S_word not_E_word{0,1000} word))
请注意,在某些情况下,仅需要前瞻。还要注意的是,以文字字符开头的模式比以后顾断言开头更有效,这就是为什么我在单词后面(即使需要在断言中再次重写该单词)放置它的原因。
(*SKIP)(*F)
在Java上不起作用。但是在Java中有其他hack方式可以规避可变长度lookbehind。 - anubhava