Problem with look behind assertion and optional substring
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/
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. ;)
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
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 '
精彩评论