正则表达式中的异或运算

37

寻求正则表达式帮助。我想设计一个可以匹配"foo" 或 "bar", 但不是同时含有"foo" 和 "bar"的字符串表达式。

如果我使用类似的表达式...

/((foo)|(bar))/

它将匹配 "foobar"。但这不是我要找的内容。那么,我该如何使正则表达式仅在其中一个条件存在时匹配?

谢谢!


foofoobar是否匹配,因为它包含"foo"和"foobar"? "foonbar"呢?您能提供匹配和非匹配的示例吗? - Thomas Owens
匹配项: "foo","bar" 非匹配项: "foofoo" "barfoo" "foobarfoo" "barbar" "barfoofoo" - SocialCensus
3
如果你不希望"foofoo"被匹配,那么你实际上并没有在谈论一个异或。 - cjm
13个回答

45

2
比起被接受的答案,这个解决方案更加优雅,特别是当你有超过两种情况时。 - Manuel Arwed Schmidt
1
你为什么加了 {1},这是什么意思? - oriadam
9
这是错误的,它只意味着 foobar 应该只被匹配一次。 - Karl
4
我同意 @Karl 的观点,这不是异或运算。它只是检查整个字符串是否为"foo"或"bar"。 - ecdani
1
你不需要{1},因为它表示应该重复1次。只需要/^(foo|bar)$/就足够了。这个正则表达式之所以有效是因为你使用了^$,与重复无关。 - chharvey

20
如果你的正则表达式语言支持,可以使用负向零宽断言:

使用负向零宽断言

(?<!foo|bar)(foo|bar)(?!foo|bar)

这将匹配不紧接着或不紧随“foo”或“bar”的“foo”或“bar”,我认为这正是您想要的。

从您的问题或示例中无法确定您要匹配的字符串是否可以包含其他标记:“foocuzbar”。如果是这样,这个模式将无法工作。

以下是测试用例的结果(“true”表示在输入中找到了该模式):

foo: true
bar: true
foofoo: false
barfoo: false
foobarfoo: false
barbar: false
barfoofoo: false

10

你可以用一条正则表达式完成这个任务,但出于可读性的考虑,我建议你做如下处理...

(/foo/ and not /bar/) || (/bar/ and not /foo/)

2
确实,我很确定我会将XOR逻辑放入代码本身,而不是正则表达式中。 - Pistos
3
如果你的编程语言支持异或运算符,最好使用 /foo/ xor /bar/。 (Perl 支持此操作符。) - cjm
1
@Ralf 这不是一个单一的表达式,而是两个用逻辑 OR 运算符连接起来的表达式。 - Ed Guiness

8
这将接受'foo'和'bar',但不接受'foobar'、'blafoo'和'blabar':
/^(foo|bar)$/

^ = mark start of string (or line)
$ = mark end of string (or line)

这将接受 'foo' 和 'bar' 和 'foo bar' 和 'bar-foo',但不接受 'foobar'、'blafoo' 和 'blabar':

/\b(foo|bar)\b/

\b = mark word boundry

3
您没有指定除“foo”和“bar”以外的内容行为,或在缺少另一个的情况下重复其中一个。例如,“food”或“barbarian”是否匹配?
假设您想匹配仅包含“foo”或“bar”中的一个实例但不是两者都有,也不是同一个的多个实例,并且不考虑字符串中的其他内容(即,“food”匹配而“barbarian”不匹配),则可以使用返回找到的匹配项数量的正则表达式,并仅在找到确切的一次匹配时视为成功。例如,在Perl中:
@matches = ($value =~ /(foo|bar)/g)  # @matches now hold all foos or bars present
if (scalar @matches == 1) {          # exactly one match found
  ...
}

如果允许多次重复相同的目标(例如,“barbarian”匹配),则可以使用相同的一般方法,然后遍历匹配列表,以查看匹配是否全部重复相同的文本,或者其他选项是否也存在。

2

2
如果你想要一个真正的异或操作,我建议在代码中实现而不是在正则表达式中。在Perl中:
/foo/ xor /bar/

但是你的评论:

匹配: "foo", "bar" 非匹配: "foofoo" "barfoo" "foobarfoo" "barbar" "barfoofoo"

表明你实际上并不是在寻找排他或。你的意思是“/foo|bar/是否只匹配一次?”

my $matches = 0;
while (/foo|bar/g) {
  last if ++$matches > 1;
}

my $ok = ($matches == 1)

1

我知道这是一个晚期的条目,但只是为了帮助其他可能正在寻找的人:

(/b(?:(?:(?!foo)bar)|(?:(?!bar)foo))/b)

0

我认为这不能通过一个正则表达式完成。边界可能有用也可能无用,这取决于你匹配的内容。

我会分别使用每个正则表达式进行匹配,并对结果执行异或操作。

foo = re.search("foo", str) != None
bar = re.search("bar", str) != None
if foo ^ bar:
    # do someting...

0
我会使用类似这样的代码。它只是检查单词周围的空格,但如果你使用\w,你可以使用\b\B来检查边界。这将匹配 " foo " 或 " bar ",所以显然你也必须替换空格,以防万一。(假设你要替换任何东西。)
/\s((foo)|(bar))\s/

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