Обратное отношение регулярных выражений Vim / sed в шаблоне поиска


Vim help говорит, что:

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. 

Похоже, что обратная связь может быть использована в шаблоне поиска. Я начал играть с ним и заметил поведение, которое не могу объяснить. Вот мой файл:

<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>

Я хотел сопоставить строки, в которых совпадают открывающие и закрывающие теги, т. е.:

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

И мое регулярное выражение теста:

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

Но это соответствует строкам: 1, 3 и 4. То же самое с sed :

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

Это: <([^ >]+) должен быть жадным и при попытке сопоставить его без 1 в конце, то все группы правильны. Но когда я добавляю 1, кажется, что <([^ >]+) становится не жадным, и он пытается заставить матч в 3-й строке. Может кто-нибудь объяснить, почему он соответствует 3rd строке:

<aza> Some text </az>

Это также regex101 demo

Примечание Речь идет не о самом регулярном выражении (вероятно, есть другой способ сделать это), а о поведении этого регулярного выражения.

3 2

3 ответа:

Чтобы понять, почему ваше регулярное выражение ведет себя так, как оно ведет себя, вам нужно понять, что делает механизм обратного следования регулярных выражений.

Движок будет жадно подбирать и потреблять столько символов, сколько сможет. Но если он не находит соответствия, он возвращается назад и пытается найти другое соответствие, которое все еще удовлетворяет шаблону.
%s,<\([^ >]\+\).*<\/\1>,,gn

Для третьей строки <aza> Some text </az>,

Движок регулярных выражений смотрит на \1 = aza. и видит, совпадает ли .*</aza> с остальной строкой. Это не так, поэтому он выбирает что-то остальное за \1. В следующий раз он выбирает \1 = az и видит, совпадает ли .*</az> с остальной строкой, и он это делает. Таким образом, строка соответствует

(это упрощенная версия. Я пропустил тот факт, что .* потенциально может сделать много отступлений сам)


Решение этой задачи так же просто, как добавление якоря в регулярное выражение, останавливающее регулярное выражение от поиска других значений, которые могли бы удовлетворить \1. В этом случае достаточно совпадения пространства или >.

Вам нужно добавить \>, чтобы указать конец слова. Могут быть и другие решения с узорами 0-ширины, но это все усложнит.

Кроме того, ваш разделитель ,, а не /

Что дает:

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

В настоящее время причина, по которой строка 3 (<aza>) отображается как совпадение, заключается в том, что термин .* в вашем регулярном выражении может совпадать через несколько строк. Таким образом, строка 3 совпадает, потому что строка 5 имеет закрывающий тег. Чтобы исправить это, заставьте регулярное выражение найти соответствующий закрывающий тег на той же самой строке только:

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