views:

123

answers:

3

I'm trying to write some regex that will parse information from alerts generated by Hyperic HQ. The alerts come in as emails with a subject line like:

"[HQ] !!! - Alert: My Demo Website Alert Resource: demo.myserver.net Apache Web Server State: fixed"

To cut a very long story short, I need to be able to consistently grab the "Apache Web Server" part, regardless of the hostname which may not even be present. I do know that the hostname will always end "myserver.net" though.

The regex I have is:

/Resource:\s.*(?<=mydomain.net)?\s(.*)\s(?=State)/

I was expecting that this would match zero or more characters between "Resource:" and "State:", optionally following (but not including) a hostname.

Unfortunately, what it returns is "Server", i.e. the last word of the bit I want to match. This happens regardless of whether the hostname is in the string.

Can anyone help?

EDIT: Solution as supplied by Chad below

/Resource:\s(?:.*.myserver.net)?(.*)\sState/ 
+2  A: 

This appears to work with the tests I wrote

/Resource:\s(?:.*myserver.net)?(?<PartIWant>.*)\s(?:State)/

It will be in the named capture group "PartIWant" if your regex engine supports named capture groups.

EDIT: I've tested this regex with both of these strings

[HQ] !!! - Alert: My Demo Website Alert Resource: demo.myserver.net Apache Web Server State: fixed
[HQ] !!! - Alert: My Demo Website Alert Resource: Apache Web Server State: fixed
Chad
Download expresso, put all your possible strings in the test data, and run this and see if it matches correctly
Chad
+1. You probably don't need to use a lookahead for "State" either.
Alan Moore
@Alan, didn't even notice that, you're right, it doesn't need to be there
Chad
@HappySpaceInvader, did you change the "myserver.net" part to your domain name? Because I've tested it with several different strings and it's returning the proper match in the named capture group "PartIWant"
Chad
Thanks - my regex skills are rather rusty and I had forgotten about the simple non-capture group (?:foo). Removing the redundant lookahead and named capture (which wouldn't work in my particular case anyway), I found the following works:/Resource:\s(?:.*.myserver.net)?(.*)\sState/
HappySpaceInvader
@HappySpaceInvader, I just noticed a small difference, in the matches, in the case where the domain exists, the match contains a leading space " Apache Web Server" as opposed to "Apache Web Server". Change the non capturing domain to include that space '(?:.*.myserver.net\s)'
Chad
+1  A: 

This is an example of the anti-pattern I call Premature Recourse to Lookaround. You know the string you're looking for is preceded by foo and followed by bar, and you know regexes have things called lookbehinds and lookaheads, so it seems obvious that's what you should use:

(?<=foo).*(?=bar)

Beware the obvious; very little about regexes is intuitive. Remember that lookaheads were a fairly late addition to regexes, and lookbehinds even later, but people were solving this kind of problem long before they came along. They did it by using capturing groups, and that's still the best option in most cases:

foo(.*)bar

There's also an outright error in your regex: the ? quantifier on the lookbehind:

(?<=mydomain.net)?

EditPadPro's search box flags that as an error, as does PHP; Java and .NET don't, but I believe they should. It makes no more sense than \b* or ^+ or ${3,7}. Those are all zero-width assertions, which means they match nothing, so by adding a quantifier you're trying to match the same nothing multiple times (remember that $ doesn't match a newline, just the position between the newline and the preceding character).

There's no danger of getting stuck in an infinite loop, but it's a good indication that the regex author has made a typo or has misunderstood something. This is especially true when the quantifier is one that can match zero times, like ? or *. It makes the assertion optional, and an optional assertion is an irrelevant assertion. In your regex, (?<=mydomain.net)? means "either the current position is preceded by mydomain.net or it isn't; I don't care either way."

Anyway, Chad has already come up with a regex that works; I just wanted to provide some insight into why yours didn't. And field-test my anti-pattern, of course. ;)

Alan Moore
That's not quite the problem I was presenting. I am looking for a string preceded by "foo" and "bar" and followed by "japh"... in which "bar" may or may not be present, but if it *is* present, I don't want to capture it.
HappySpaceInvader
By "foo" and "bar" I assume you mean `Resource:` and the hostname, and by "japh", `State:`; that doesn't matter. The point is that you don't need lookarounds to match any of those things; just match them "straight" and use the capture group to extract the part that interests you. If you weren't allowed to use a capture group you would have to get creative with lookarounds, but fortunately that's not the case.
Alan Moore
+1  A: 

Sometimes, things can be done simple. In your favourite language, do a split on "myserver.net", then do a split on "State:" of the first element. eg in Python

>>> s="""[HQ] !!! - Alert: My Demo Website Alert Resource: demo.myserver.net Apache Web Server State: fixed"""
>>> s.split("myserver.net")[-1].split("State:")[0]
' Apache Web Server '
ghostdog74
Ah, but I'm limited to regex - in the long version of the backstory that I ommitted from my original post. Sorry for not making that clear.
HappySpaceInvader