Not understanding a RegEx with Non-Capturing Group - regex

I need help in understanding why a specific regular expression works and another one doesn't .. here's the background:
I want to use the ansible lineinfile module to add/modify the -u ntpd:ntpd option to the OPTIONS="" field in /etc/sysconfig/ntpd.
I see three cases:
No line with OPTIONS at all --> add OPTIONS="-u ntpd:ntpd"
OPTIONS line already there, but not with that specific option --> add the option to the existing options
OPTIONS line existing, -u option existing but with wrong parameters --> change the parameters
My first shot was this:
- name: Make sure ntpd runs as user:group ntpd:ntpd
lineinfile:
path: /etc/sysconfig/ntpd
regexp: 'OPTIONS=\"(.*)(-u\s[A-Za-z0-9\-_]+:[A-Za-z0-9\-_]+)?(.*)\"'
line: 'OPTIONS="\1 \3 -u ntpd:ntpd'
backrefs: yes
But the first group contains everything between "" Example at regex101
Changing the 1st and 3rd group to lazy makes everything between "" belong to the third group Example at regex101
After trying around and looking at it with a colleague we came up with this regular expression which does what we want:
OPTIONS=\"(?:(.*)(-u [a-z]+:[a-z]+))?(.*?)\"
Example at regex101
But honestly, we do not understand why. Anyone out there who can shed some light on this?

Your culprits are the wildcards in your first and third matching groups! Let's have a look at each one. We'll use OPTIONS="-x -u wrong:wrong -c blah" as the string to test.
OPTIONS=\"(.*)(-u\s[A-Za-z0-9\-_]+:[A-Za-z0-9\-_]+)?(.*)\"
This regex starts with a greedy wildcard, and in a way, that's where it ends, too. Between your two quote marks must appear:
First capture group: as much of anything as it wants. This group can, will, and does eat your entire string, leaving nothing else behind. It'll give back if it has to, but...
Second capture group: the interesting part of the regex. Normally, this would capture what you want to see. However, since it's marked with the ? quantifier, it's allowed to exist zero or one times - and the greedy quantifier is happy to keep its catch if the second capture group is not required to exist.
Third capture group: same issue here. Since .* means "zero or more" of any character, and the first greedy wildcard ate them all, it's happy to match nothing, which is why your first regex example has the dotted red line just before the closing quote - it signifies group 3's empty match.
Result: the first greedy wildcard ate everything, and none of the other capture groups made it a requirement to give something back.
OPTIONS=\"(.*?)(-u\s[A-Za-z0-9\-_]+:[A-Za-z0-9\-_]+)?(.*?)\"
In contrast, your second regex contains lazy wildcards. These won't eat anything if they don't have to.
First CG: doesn't capture anything if it doesn't specifically have to. It won't capture anything unless we get to the very end of the string and find no matches, at which point it wakes up and starts eating things until a match can be found.
Second CG: again, it can exist, but because of the ? quantifier, it doesn't have to. Since it can't instantly match the -x at the start of the string, it decides to try the final CG.
Third CG: doesn't capture anything if it doesn't specifically have to.
Except... now there's no match. At this point, the regex engine starts working backwards.
Third CG: starts capturing things, increasing until it hits a match. In this case, since your second group doesn't immediately match, it assumes it won't match at all (instead of checking to see if it can make something work with the first wildcard CG) and instead eats all the text. This counts as a match, and the engine is satisfied.
Second CG - tries to match, but doesn't if it can't immediately do so. Note that on the second line, where the first text after OPTIONS=" is a match, this group does activate.
First CG: never reached, the third one had it handled.
Result: you're getting closer, but because the lazy wildcard doesn't capture unless it has to, and the engine is more willing to let your third CG eat everything than it is to try and force a match between the second and first groups, the third group gets it all.
OPTIONS=\"(?:(.*)(-u [a-z]+:[a-z]+))?(.*?)\"
Now you got it. The non-capture group at the start treats the first two capture groups as the same entity. Let's look at what happens now:
First NCG:
First nested CG: matches the entire string.
Second nested CG: forces the first nested CG to surrender some text so it can match. There's no ? quantifier this time, so it's not an option: the second CG must match. Because the entire NCG must try to find a match before the rest of the regex can continue matching, if a match exists, it's guaranteed to be properly found.
Second CG: cleans up whatever the first NCG left behind.
Result: you get the string. Woo!
Let's take a closer look at your non-capturing group and see why it works.
(?:(.*)(-u [a-z]+:[a-z]+))?
(.*)(-u [a-z]+:[a-z]+)
Notice something? Your ? quantifier is outside the non capture group. This means that inside the NCG, your second capture group is no longer optional. The regex is forced to try to match your second CG, and if it can't, the ? quantifier outside the NCG cancels the entire thing, including the first wildcard CG. This means that the first wildcard CG can't be used to gobble text if the engine feels like ignoring your second CG - it can now only be used to assist the second CG in matching, as was likely your original intent.
The ? quantifier on the second CG was necessary, because there was no guarantee the command would exist. However, this gave the engine the option of being lazy and just ignoring it altogether - which it will gladly do, especially if you place it right next to a wildcard CG.
As an aside, if you change your regex like so, and put the ? quantifier back where it was, next to your second CG...
OPTIONS=\"(?:(.*)(-u [a-z]+:[a-z]+))?(.*?)\" works
OPTIONS=\"(?:(.*)(-u [a-z]+:[a-z]+)?)(.*?)\" uh oh
^^
...It ignores your second group again, and you're right back where you started.
Good luck ;)

First regex : the first capturing group's .* is greedy, it will match as much as possible, and it is possible to match everything up to the double-quote since everything else that follows is optional.
Second regex : the first capturing group's .*? is lazy, it will match as little as possible. It first tries matching nothing, which is possible since the last capturing group will be able to match the rest of the string.
Third regex : the non-capturing group is greedy and will try to match if possible, and its -u group is mandatory. The regex engine will match everything with .*, then backtrack until it's possible to match the -u group. If there's no -u, it leaves the whole thing to match to the third group.
Note that the laziness of the third group isn't necessary in your third regex, and that making the first .* lazy will improve performances : https://regex101.com/r/xvYOoF/7
As an alternative the following regex that makes sure the first capturing group can't match a -u option might be more understandable and will be more performant especially on long options strings (not that it matters I guess) :
OPTIONS="((?:-[^u]|[^-])*)(-u \w+:\w+)?(.*)"

Related

Complicated regex to match anything NOT within quotes

I have this regex which scans a text for the word very: (?i)(?:^|\W)(very)[\W$] which works. My goal is to upgrade it and avoid doing a match if very is within quotes, standalone or as part of a longer block.
Now, I have this other regex which is matching anything NOT inside curly quotes: (?<![\S"])([^"]+)(?![\S"]) which also works.
My problem is that I cannot seem to combine them. For example the string:
Fred Smith very loudly said yesterday at a press conference that fresh peas will "very, very defintely not" be served at the upcoming county fair. In this bit we have 3 instances of very but I'm only interested in matching the first one and ignore the whole Smith quotation.
What you describe is kind of tricky to handle with a regular expression. It's difficult to determine whether you are inside a quote. Your second regex is not effective as it only ignores the first very that is directly to the right of the quote and still matches the second one.
Drawing inspiration from this answer, that in turn references another answer that describes how to regex match a pattern unless ... I can capture the matches you want.
The basic idea is to use alternation | and match all the things you don't want and then finally match (and capture) what you do want in the final clause. Something like this:
"[^"]*"|(very)
We match quoted strings in the first clause but we don't capture them in a group and then we match (and capture) the word very in the second clause. You can find this match in the captured group. How you reference a captured group depends on your regex environment.
See this regex101 fiddle for a test case.
This regex
(?i)(?<!(((?<DELIMITER>[ \t\r\n\v\f]+)(")(?<FILLER>((?!").)*))))\bvery\b(?!(((?<FILLER2>((?!").)*)(")(?<DELIMITER2>[ \t\r\n\v\f]+))))
could work under two conditions:
your regex engine allows unlimited lookbehind
quotes are delimited by spaces
Try it on http://regexstorm.net/tester

Alternation usage creates strange behavior

I am using this regex to catch the "e"s at the end of a string.
e\b|e[!?.:;]
It works but the thing I don't understand, when this encounters an input like
"space."
It only takes the "e", not including the "." but the regex has [!?.:;], which suggests it should capture the dot also.
If I remove the e\b| in the beginning, it captures the dot too. This is no problem for me because I was already trying to capture the letter only, however, I need this behavior to be explained.
The regex engine stops searching as soon as it finds a valid match.
The order of the alternatives matters, and since e is first matched, the engine will stop looking for the right side of the alternation.
In your case, the regex engine starts at the first token in "space.", it doesn't match. Then it moves to the second one, the "p". It still doesn't match.. It keeps trying to match tokens until it finally reaches the "e", and matches the left side of the alternation - when this happens, it doesn't proceed since a match was found.
I highly advise you to go through this tutorial, it gives a very good explanation on that.
If you need to make sure the . is returned in the match, just swap the alternatives:
e[!?.:;]|e\b
In NFA regex, the first alternative matched wins. There are also some different aspects here to consider, too, but this is out of scope here.
More details can be found here:
Why regex engine choose to match pattern ..X from .X|..X|X.?
Lazy quantifier {,}? not working as I would expect
In this case, here is what is going on: \b after e requires a non-word character after it. Since . is a non-word character, it satisfies the condition, that is why e\b (being the first alternative branch) wins with e[!?.:;] as both are able to match the same substring at that location.

Regex PCRE: Validate string to match first set of string instead of last

I tried quite a few things but Im stuck with my regex whenever meets the criteria 2 consecutive times. In this case it just considers it as one expressions instead of 2.
\[ame\=[^\.]+(.+)youtube\.(.+)v\=([^\]\&\"]+)[\]\'\"\&](.+)\[\/ame\]
E.g.
[ame="http://www.youtube.com/watch?v=brfr5CD2qqY"][B][COLOR=yellow]http://www.youtube.com/watch?v=brfrx5D2qqY[/COLOR][/B][/ame][/U]
[B][COLOR=yellow]or[/COLOR][/B] [B][COLOR=yellow]B[/COLOR][/B]
[ame="http://www.youtube.com/watch?v=M9ak3rKIBAU"][B][COLOR=yellow]http://www.youtube.com/watch?v=M9a3arKIBAU[/COLOR][/B][/ame]
[B][COLOR=yellow]or[/COLOR][/B] [B][COLOR=yellow]C[/COLOR][/B]
[ame="http://www.youtube.com/watch?v=7vh--3pyq5U"][COLOR=yellow]http://www.youtube.com/watch?v=7vh--3pyq5U[/COLOR][/ame]
In that case, this regex would instead of matching all 3 options, it takes it as one.
Any ideas how to make an expression that would say match the first "[/ame]"?
The problem is the use of .+ - they are "greedy", meaning they will consume as much input as possible and still match.
Change them to reluctant quantifiers: .+?, which won't skip forward over the end of the first match to match the end if the last match.
I'm not sure what your objective is (you haven't made that clear yet)
But this will match and capture out the youtube URL for you, ensuring you only match each single instance between [ame= and [/ame]
/\[ame=["'](.*?)["'](.*?)\/ame\]/i
Here's a working example, and a great sandbox to play around in: http://regex101.com/r/jR4lK2

Notepad++ regex group capture

I have such txt file:
ххх.prontube.ru
salo.ru
bbb.antichat.ru
yyy.ru
xx.bb.prontube.ru
zzz.com
srfsf.jwbefw.com.ua
Trying to delete all subdomains with such regex:
Find: .+\.((.*?)\.(ru|ua|com\.ua|com|net|info))$
Replace with: \1
Receive:
prontube.ru
salo.ru
antichat.ru
yyy.ru
prontube.ru
zzz.com
com.ua
Why last line becomes com.ua instead of jwbefw.com.ua ?
This works without look around:
Find: [a-zA-Z0-9-.]+\.([a-zA-Z0-9-]+)\.([a-zA-Z0-9-]+)$
Replace: \1\.\2
It finds something with at least 2 periods and only letters, numbers, and dashes following the last two periods; then it replaces it with the last 2 parts. More intuitive, in my opinion.
There's something funny going on with that leading xxx. It doesn't appear to be plain ASCII. For the sake of this question, I'm going to assume that's just something funny with this site and not representative of your real data.
Incorrect
Interestingly, I previously had an incorrect answer here that accumulated a lot of upvotes. So I think I should preserve it:
Find: [a-zA-Z0-9-]+\.([a-zA-Z0-9-]+)\.(.+)$
Replace: \1\.\2
It just finds a host name with at least 2 periods in it, then replaces it with everything after the first dot.
The .+ part is matching as much as possible. Try using .+? instead, and it will capture the least possible, allowing the com.ua option to match.
.+?\.([\w-]*?\.(?:ru|ua|com\.ua|com|net|info))$
This answer still uses the specific domain names that the original question was looking at. As some TLD (top level domains) have a period in them, and you could theoretically have a list including multiple subdomains, whitelisting the TLD in the regex is a good idea if it works with your data set. Both current answers (from 2013) will not handle the difference between "xx.bb.prontube.ru" and "srfsf.jwbefw.com.ua" correctly.
Here is a quick explanation of why this psnig's original regex isn't working as intended:
The + is greedy.
.+ will zip all the way to the right at the end of the line capturing everything,
then work its way backwards (to the left) looking for a match from here:
(ru|ua|com\.ua|com|net|info)
With srfsf.jwbefw.com.ua the regex engine will first fail to match a,
then it will move the token one place to the left to look at "ua"
At that point, ua from the regex (the second option) is a match.
The engine will not keep looking to find "com.ua" because ".ua" met that requirement.
Niet the Dark Absol's answer tells the regex to be "lazy"
.+? will match any character (at least one) and then try to find the next part of the regex. If that fails, it will advance the token, .+ matching one more character and then evaluating the rest of the regex again.
The .+? will eventually consume: srfsf.jwbefw before matching the period, and then matching com.ua.
But the implimentation of ? also creates issues.
Adding in the question mark makes that first .+ lazy, but then causes group1 to match bb.prontube.ru instead of prontube.ru
This is because that first period after the bb will match, then inside group 1 (.*?) will match bb.prontube. before \.(ru|ua|com\.ua|com|net|info))$ matches .ru
To avoid this, change that third group from (.*?) to ([\w-]*?) so it won't capture . only letters and numbers, or a dash.
resulting regex:
.+?\.(([\w-])*?\.(ru|ua|com\.ua|com|net|info))$
Note that you don't need to capture any groups other than the first. Adding ?: makes the TLD options non-capturing.
last change:
.+?\.([\w-]*?\.(?:ru|ua|com\.ua|com|net|info))$
Search what: .+?\.(\w+\.(?:ru|com|com\.au))
Replace with: $1
Look in the picture above, what regex capture referring
It's color the way you will not need a regex explaination anymore ....

Does the greediness of a regex matter after all need matches have been used?

The title pretty much says it all.
I have a regex that I need to match the names of virtual machines from an array.
The regex looks like this:
/^(?<id> \d+)\s+(?<name> .+?)\s+\[.+\]/mx
After the last capture group is matched I have no need for the left overs other than using them to stop the match at the correct place so that all characters in the capture group are correctly matched. Does it matter how greedy the left overs are if they are not being used?
Here is an example of the string I am matching, this is before the match.
432 TEST Box åäö!"''*# [Store] TEST Box +w6XDpMO2IQ-_''_+Iw/TEST Box +w6XDpMO2IQ-_''_+Iw.vmx slesGuest vmx-04
Here is an example if the string I am matching, this is after the match.
432 TEST Box åäö!"''*#
Like I ask above, if I only need the first 2 capture groups does it matter how greedy the uncaptured part at the end is?
There would be no difference between \s+ and \s+? as long as the preceding quantifier .+? remains lazy; it will always match at least one space and expand as needed until the following [.
I first said that there might be a difference between \[.+\] and \[.+?\] if more than one pair of data items can occur on the same line. The former would match too much in that case. But I just noticed that you've anchored your regex to the start of the line. So no, in that case, it doesn't matter either.