4

Vim help says that:

\1      Matches the same string that was matched by     */\1* *E65*
        the first sub-expression in \( and \). {not in Vi}
        Example: "\([a-z]\).\1" matches "ata", "ehe", "tot", etc. 

It looks like the backreference can be used in search pattern. I started playing with it and I noticed behavior that I can't explain. This is my file:

<paper-input label="Input label"> Some text </paper-input>
<paper-input label="Input label"> Some text </paper-inputa>
<aza> Some text </az>
<az> Some text </az>
<az> Some text </aza>

I wanted to match the lines where the opening and closing tags are matching i.e.:

<paper-input label="Input label"> Some text </paper-input>
<az> Some text </az>

And my test regex is:

%s,<\([^ >]\+\).*<\/\1>,,gn

But this matches lines: 1, 3 and 4. Same thing with sed:

$ sed -ne 's,<\([^ >]\+\).*<\/\1>,\0,p' file
<paper-input label="Input label"> Some text </paper-input>
<aza> Some text </az>
<az> Some text </az>

This: <\([^ >]\+\) should be greedy and when trying to match it without \1 at the end then all the groups are correct. But when I add \1 it seems that <\([^ >]\+\) becomes not greedy and it tries to force the match in 3rd line. Can someone explain why it matches 3rd line:

<aza> Some text </az>

This is also a regex101 demo

NOTE This is not about the regex itself (probably there is other way to do it) but about the behavior of that regex.

5
  • 4
    You should take a look at backtracking engines. If it doesn't find a match the engine backtracks until and chooses something different. For instance \1 equals az on line three after all of the backtracking. (Since you never added anchors) Commented Sep 8, 2016 at 1:43
  • 1
    to add to @FDinoff's point, you can add a rule to match a space or > as anchors... <\([^ >]\+\)[ >].*<\/\1> Commented Sep 8, 2016 at 2:21
  • @FDinoff This is intresting. I didn't know about it. Commented Sep 8, 2016 at 7:32
  • 1
    @spasic Yes, I understood how backtracking works and the anchors for space and > seem to be the best idea here. Commented Sep 8, 2016 at 7:34
  • @FDinoff If you add this as an answer I will mark. Commented Sep 8, 2016 at 16:17

3 Answers 3

4

To understand why your regex behaves the way it does you need to understand what a backtracking regex engine does.

The engine will greedily match and consume as many characters as it can. But if it doesn't find a match it goes back and tries to find a different match that still satisfies the pattern.

%s,<\([^ >]\+\).*<\/\1>,,gn

For line three <aza> Some text </az>,

The regex engine looks at \1 = aza. and sees if .*</aza> matches the rest of the string. It doesn't so it chooses something else for \1. The next time it chooses \1 = az and sees if .*</az> matches the rest of the string and it does. So the string matches

(This is a simplified version. I skipped over the fact that .* can potentially do a lot of backtracking itself)


Solving it is as easy as adding an anchor in the regex stops the regex from searching for other values that could satisfy \1. In this case matching a space or > is sufficient.

Sign up to request clarification or add additional context in comments.

1 Comment

This is very good explanation. Ending word with \> i.e. <\([^ >]\+\)\>.*</\1> as suggested by @LucHermitte will also work.
2

You need to add \> to indicate end of word. There may be other solutions with 0-width patterns, but it'll complicates things.

Also, your separator is ,, not /

Which gives:

%s,<\([^ >]\+\)\>.*</\1>,,gn

5 Comments

This won't match 1st line. Besides, as I mentioned in the question - I want to understand why my regex is not working.
I've just checked. This does match first line (I've just checked my gvim 7.4-2207, and vim 7-4-2181 I have at hand). Regex 101 doesn't handle it well though. Regarding the explanation, @FDinoff already gave it.
@DawidGrabowski, This works as expected with vim 7.3-429 as well. Could it be that you've altered &isk definition?
Instead of copying the regex I did write it and make a mistake. It's working (sed and vim)
That happens :)
0

Currently the reason why line 3 (<aza>) is showing up as a match is that the .* term in your regex can match across multiple lines. So line 3 matches because line 5 has the closing tag. To correct this, force the regex to find a matching closing tag on the same line only:

%s,<\([^ >]\+\)[^\n]*?<\/\1>,,gn
               ^^^^^ use [^\n]* instead of .*

4 Comments

Why do you think .* is matching accross multiples lines? It matches any character except new line
@DawidGrabowski Then how do you explain line 3 is showing up as a match?
I don't know. This is why I asked this question. I know that .* is definitely not matching new line. I added regex101 demo.
\_. can match a newline with vim. not .

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.