tags:

views:

1572

answers:

11

Hey Folks! Looking for a bit of regex help. Id like to design an expression that matches a string with "foo" OR "bar" but not both "foo" AND "bar"

If I do something like...

/((foo)|(bar))/

Itll match "foobar". Not what Im looking for. So, how can I make regex match only when one term or the other is present?

Thanks!

+6  A: 

You can do this with a single regex but I suggest for the sake of readability you do something like...

(/foo/ and not /bar/) || (/bar/ and not /foo/)
Ed Guiness
Indeed, I'm pretty sure I would put the XOR logic into the code itself, and not in the regexp.
Pistos
Or even better, /foo/ xor /bar/, if your language has an XOR operator. (Perl does.)
cjm
This seems to be the best solution, Thanks!
SocialCensus
A: 

I'd use something like this. It just checks for space around the words, but you could use the \b or \B to check for a border if you use \w. This would match " foo " or " bar ", so obviously you'd have to replace the whitespace as well, just in case. (Assuming you're replacing anything.)

/\s((foo)|(bar))\s/
The Wicked Flea
A: 

Can you clarify what text you are matching against? The regex you supplied matches:

  • /foo/
  • /bar/

But does NOT match:

  • /foobar/

Which seems to be the behavior you want. Just curious.

James D
+1  A: 

I don't think this can be done with a single regular expression. And boundaries may or may not work depending on what you're matching against.

I would match against each regex separately, and do an XOR on the results.

foo = re.search("foo", str) != None
bar = re.search("bar", str) != None
if foo ^ bar:
    # do someting...
Can Berk Güder
A: 

I tried with Regex Coach against:

x foo y
x bar y
x foobar y

If I check the g option, indeed it matches all three words, because it searches again after each match.
If you don't want this behavior, you can anchor the expression, for example matching only on word boundaries:

\b(foo|bar)\b

Giving more context on the problem (what the data looks like) might give better answers.

PhiLho
A: 
\b(foo)\b|\b(bar)\b

And use only the first capture group.

Andrew Cowenhoven
A: 

You might want to consider the ? conditional test.

(?(?=regex)then|else)

Regular Expression Conditionals

Josh
+7  A: 

If your regex language supports it, use negative lookaround:

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

This will match "foo" or "bar" that is not immediately preceded or followed by "foo" or "bar", which I think is what you wanted.

It's not clear from your question or examples if the string you're trying to match can contain other tokens: "foocuzbar". If so, this pattern won't work.

Here are the results of your test cases ("true" means the pattern was found in the input):

foo: true
bar: true
foofoo: false
barfoo: false
foobarfoo: false
barbar: false
barfoofoo: false
erickson
Thanks for teaching me about negative lookarounds :)
SocialCensus
this works perfectly but not in Perl, fyi
Keng
A: 

Using the word boundaries, you can get the single word...

me@home ~  
$ echo "Where is my bar of soap?" | egrep "\bfoo\b|\bbar\b"  
Where is my bar of soap?  

me@home ~  
$ echo "What the foo happened here?" | egrep "\bfoo\b|\bbar\b"  
What the foo happened here?  

me@home ~  
$ echo "Boy, that sure is foobar\!" | egrep "\bfoo\b|\bbar\b"
bill_the_loser
+3  A: 

You haven't specified behaviour regarding content other than "foo" and "bar" or repetitions of one in the absence of the other. e.g., Should "food" or "barbarian" match?

Assuming that you want to match strings which contain only one instance of either "foo" or "bar", but not both and not multiple instances of the same one, without regard for anything else in the string (i.e., "food" matches and "barbarian" does not match), then you could use a regex which returns the number of matches found and only consider it successful if exactly one match is found. e.g., in Perl:

@matches = ($value =~ /(foo|bar)/g)  # @matches now hold all foos or bars present
if (scalar @matches == 1) {          # exactly one match found
  ...
}

If multiple repetitions of that same target are allowed (i.e., "barbarian" matches), then this same general approach could be used by then walking the list of matches to see whether the matches are all repeats of the same text or if the other option is also present.

Dave Sherohman
+1  A: 

If you want a true exclusive or, I'd just do that in code instead of in the regex. In Perl:

/foo/ xor /bar/

But your comment:

Matches: "foo", "bar" nonmatches: "foofoo" "barfoo" "foobarfoo" "barbar" "barfoofoo"

indicates that you're not really looking for exclusive or. You actually mean "Does /foo|bar/ match exactly once?"

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

my $ok = ($matches == 1)
cjm