正则表达式:是否可能通过排除匹配而不使用向前查看?

32
在某些正则表达式引擎中,不支持[否定]零宽断言(前瞻/后顾)。这使得排除某些内容变得极其困难(或许是不可能的?)。例如,“每行都没有“foo”的行”,就像这样:
^((?!foo).)*$

没有使用回顾(忽略复杂性和性能问题)可以实现相同的结果吗?


2
点击“regex-negation”标签以查看一些类似的问题。 - finnw
4个回答

31

更新:正如@Ciantic在评论中指出的那样,它会失败“在oo之前有两个ff”。


^(f(o[^o]|[^o])|[^f])*$

注意:在客户端仅否定匹配比使用上述正则表达式要容易得多。

该正则表达式假设每行以换行符结尾,如果不是,则参见C++和grep的正则表达式。

Perl、Python、C++和grep中的示例程序都提供相同的输出。

  • perl

    #!/usr/bin/perl -wn
    print if /^(f(o[^o]|[^o])|[^f])*$/;
    
  • python

    #!/usr/bin/env python
    import fileinput, re, sys
    from itertools import ifilter
    
    re_not_foo = re.compile(r"^(f(o[^o]|[^o])|[^f])*$")
    for line in ifilter(re_not_foo.match, fileinput.input()):
        sys.stdout.write(line)
    
  • c++

    #include <iostream>
    #include <string>
    #include <boost/regex.hpp>
    
    int main()
    {
      boost::regex re("^(f(o([^o]|$)|([^o]|$))|[^f])*$");
      //NOTE: "|$"s are there due to `getline()` strips newline char
    
      std::string line;
      while (std::getline(std::cin, line)) 
        if (boost::regex_match(line, re))
          std::cout << line << std::endl;
    }
    
  • grep

    $ grep "^\(f\(o\([^o]\|$\)\|\([^o]\|$\)\)\|[^f]\)*$" in.txt
    

示例文件:

foo
'foo'
abdfoode
abdfode
abdfde
abcde
f

fo
foo
fooo
ofooa
ofo
ofoo

输出:

abdfode
abdfde
abcde
f

fo
ofo

1
很明显,在程序中进行后处理并否定匹配是首选方法。有时您没有选择,即使您有选择,了解您的替代方案也是很好的。 - Tomalak
2
这个正则表达式不正确。它无法匹配 ffobarf。但是这个可以:^(f(o([^o]|$)|[^o]|$)|[^f])*$ - Gumbo
1
@J.F. Sebastian:啊,你说得对。我想知道为什么他没有改变其他的。 - Gumbo
3
答案似乎不能处理在“oo”之前有两个“ff”的“somethingffoosomething”。 - Ciantic
2
不错的回答,但是foo有两个相似的字符并不意味着这个问答就是通用的。如果使用abc会更好。 - Jean-François Fabre
显示剩余5条评论

5
我发现了这个问题,并将事实上没有完全工作的正则表达式视为个人挑战。我相信我已经成功创建了一个正则表达式,可以适用于所有输入——只要您可以使用原子组/占有量词
当然,我不确定是否有任何支持原子组但不支持回顾的风格,但问题问是否在正则表达式中声明排除而不使用回顾是可能的,而且从技术上讲是可能的。
\A(?:$|[^f]++|f++(?:[^o]|$)|(?:f++o)*+(?:[^o]|$))*\Z

解释:

\A                         #Start of string
(?:                        #Non-capturing group
    $                      #Consume end-of-line. We're not in foo-mode.
    |[^f]++                #Consume every non-'f'. We're not in foo-mode.
    |f++(?:[^o]|$)          #Enter foo-mode with an 'f'. Consume all 'f's, but only exit foo-mode if 'o' is not the next character. Thus, 'f' is valid but 'fo' is invalid.
    |(?:f++o)*+(?:[^o]|$)  #Enter foo-mode with an 'f'. Consume all 'f's, followed by a single 'o'. Repeat, since '(f+o)*' by itself cannot contain 'foo'. Only exit foo-mode if 'o' is not the next character following (f+o). Thus, 'fo' is valid but 'foo' is invalid.
)*                         #Repeat the non-capturing group
\Z                         #End of string. Note that this regex only works in flavours that can match $\Z

如果由于某种原因,您无法使用占位符量词或回顾后发现,但可以使用原子分组,则可以使用:

\A(?:$|(?>[^f]+)|(?>f+)(?:[^o]|$)|(?>(?:(?>f+)o)*)(?:[^o]|$))*\Z

正如其他人所指出的那样,更实际的做法可能是通过其他方式否定匹配。

1
实际上,可以在不使用任何扩展正则表达式功能的情况下表达 ^(?!.*foo) :D 在这种情况下的解决方案是:^([^f]|(f+o)*f+([^fo]|o([^fo]|$)|$))*$。我们甚至可以相当优雅地将其扩展到任意子字符串 "foo"...... 我很快会发布有关此内容的详细说明! - jaytea
阅读这些内容变得更加复杂,因为双重的“oo”。请理解此问题的人创建一个不包含该问题的版本。 - Preston
1
@Preston 你想要一个非回顾的正则表达式来查找不包含"sna"的行吗?\A(?:$|[^s]++|s++(?:[^n]|$)|(?:s++n)*+(?:[^a]|$))*\Z - Sarov
有没有正则表达式生成器可以接受一个包含字母数字的“字符串”,并搜索不包含它的行(而不使用环视)?那么,如果要添加其他单词进行搜索呢? - Jon Grah
@JonGrah 我建议你提出一个新的问题来询问。实际上,实用的建议可能是相同的,如果你不能使用lookaround,那么就编写非正则表达式代码来完成它。 - Sarov

2
我偶然发现这个问题,正在寻找我的正则表达式排除解决方案,我试图在正则表达式中排除一个序列。对于这种情况,我的最初反应是在grep中使用-v反转匹配选项。例如,“每行都没有“foo”的行”。
grep -v foo

这将返回文件中不匹配“foo”的所有行。
这么简单,我强烈感觉我刚刚误读了你的问题...

3
grep -v foo搜索"foo"并否定结果,OP说他希望正则表达式本身完成工作。但是假设要求是“包含'foo'且*不包含'bar'”,并且你只能执行一次正则表达式匹配怎么办?简单地否定结果就行不通了。 - Alan Moore
@Alan:没错,但为什么要限制只匹配一个正则表达式呢?如果我们不限制只匹配一个,那么我们可以使用管道符号:grep foo <file> | grep -v bar。我提出这个问题是因为我无法在Emacs中弄清楚上面的示例并使其工作,但我能够在命令行上做到这一点。 - Zach Young
2
当然,如果可用的话,grep -v或相似命令是最好的选择了。但OP说的是一个假设情况,即你不能反转匹配项,也不能使用前瞻断言。幸运的是,在现实世界中,这样的情况极其罕见。;) - Alan Moore
1
@AlanMoore 你说极其罕见?这里有一个例子,而且想想自从问题被提出已经过去了7年。链接 - Dmitry Grigoryev

1
通常情况下,您可以从客户端代码中查找foo并反转正则表达式匹配的结果。
举个简单的例子,假设您想验证一个字符串是否只包含特定字符。
您可以这样写:

^[A-Za-z0-9.$-]*$

并接受true结果为有效,或者像这样:

[^A-Za-z0-9.$-]

并接受false作为有效结果。

当然,这并不总是一个选项:例如有时你必须将表达式放在配置文件中或将其传递给另一个程序。但值得记住。 例如你的具体问题,如果你可以使用否定,那么表达式会简单得多


2
我知道后处理可以解决这个问题...这就是我想避免的,我正在寻找一个能够正确处理的普通正则表达式。此外,我正在寻找一些禁止特定字符序列的东西,而不是无序集合。 - Tomalak

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