Привет, коллеги! Сегодня рассмотрим некоторые малоизвестные и экспертные возможности регулярных выражений Вим. Надеюсь, некоторые из них окажутся вам полезны.
Регулярное выражение в Вим состоит из ветвей, разделенных символом \|. Выражение совпадает, если совпадает хотя бы одна ветвь, то есть это вроде операции ИЛИ. Если совпадает более одной ветви, совпавшей считается самая левая из совпавших.
Например, subroutine\|function
Или так можно искать тригонометрические функции: sin\|cos\|tg\|tan
Каждая ветвь состоит из частей, разделенных символом \&. Совпасть должны все части в одной и той же позиции (это как И) и совпадением считается самая правая. Это довольно экзотическая возможность, которой, например, нет в Перл. Она позволяет делать проверки.
Например, \d*\.\d*\&\d\+ совпадет с 42 в строке "42.666".
Или можно наложить два совпадения: .*Francesca\&.*Paolo совпадет на строке, на которой есть оба имени.
Есть такая игра: Wordlets. Там надо угадывать слово из пяти букв, неудачные попытки дают информацию о буквах на своем месте, буквах не на своем месте и буквах, которых нет в слове. Грешен, прибегаю к помощи /usr/share/dict/words и Вим. Вот здесь и помогает этот оператор И (\&):
\<[^in]\{2}\w[^io][^w]\>\&.*i\&.*n\&.*o\&.*w
Здесь написано: первая буква не i и не n, вторая тоже, третья любая, четвертая не i и не o, пятая не w. В слове пять букв. ПРИ ЭТОМ в слове есть буква i, есть буква n, а также o и w. Такое слово одно: owing. Я молодец!
Внутри частей могут быть символы, якоря и повторители-квантификаторы. Но можно целое выражение заключить в скобки и оно тогда играет роль символа.
Для такой группировки годятся и захватывающие скобки \(\), но специально для этой цели предусмотрены незахватывающие скобки \%( \). Их число неограничено (захватывающих не более девяти, ведь доступ к ним через \1—\9) и без захвата получается немного эффективнее. Например,
\%(\d\+\.\d\+\s\)\+
описывает строку таблицы чисел: цифра-точка-цифры, потом пробел, и это может повторяться один или более раз.
Квантификаторы обычно жадные — захватывают так много, как можно, лишь бы совпадение нашлось. Поэтому искать формулу в знаках $ таким выражением: \$.*\$ — ошибка. Выражение захватит всё от первого знака доллара до последнего, на данной строке.
Самый частый квантификатор * (от нуля и более), есть еще \+ (не менее одного) и синонимы \= и \? (не более одного). Можно уточнить желания: \{n,m} требует не менее n и не более m, но как можно больше. Можно опустить и n, и m: \{,m} означает "от нуля до m", \{n,} означает "не менее n", \{n} означает "ровно n".
Запомним: жадный квантификатор забирает всё, что сможет, а если совпадение не найдется, то начинает уступать по одному символу. Так, .* забирает всё до конца строки, но .*! тогда не совпадет, строка-то кончилась, а нужен еще восклицательный знак. Тогда .* отдаст последний символ. Если это он, то совпадение нашлось. Если нет, то уступит еще.
Кстати, выражение \d* на строке "март, 8" совпадет отнюдь не на восьмерке! Звездочка допускает нуль символов, в данном случае цифр: а их до буквы м как раз столько. Там совпадение и найдется. Убедитесь сами: s/8/9/ превратит строку в "9март, 8".
Есть и ленивые квантификаторы, берущие так мало, как это возможно без потери совпадения. Они все имеют вид \{-n,m}. С теми же умолчаниями, причем \{-} означает ленивую звездочку: "от нуля и более, но как можно меньше". Вот она и нужна для отлова формулы: \$.\{-}\$. На первом же долларе поиск остановится. Можно и так: \$[^$]*\$. Но это мы применили класс символов "всё, кроме доллара", что не сработает, если ограничитель не из одного символа. А ленивый квантификатор — сработает, так что лучше их знать.
Ленивый квантификатор берет минимум. Если можно, так и нуль символов. Потом совпадение ищется дальше, и если не находится, то квантификатор берет еще символ, потом еще, и так далее.
Атомарная группировка в Вим есть: это \@>. Символ или выражение в скобках перед этим символом никогда "не отступает" и не отдает захваченного.
Рассмотрим пример: выражение a*a*a*ab и текст aaaaab. Первая комбинация a* захватит все a (квантификатор жадный), две другие согласятся на нуль символов, так что пока все хорошо. Далее нужна еще одна а, но ее нет, поэтому a* отдаст один символ, совпав с меньшим числом а подряд. Эту освобожденную букву заберет вторая a*. Так они будут приключаться долго, пока, наконец, не поделят все а, кроме одной, которая совпадет со второй буквой с конца в выражении. Затем начнется поиск буквы b. Если она есть, то совпадение обнаружено, а если нет, то его и нет. Во втором случае установление этого факта может быть трудоемким. Выражение \%(a*a*a*\)\@>ab не совпадет с aaaaab вообще: первая a* заберет все a и не отдаст, и всё на этом. В данном случае в ней смысла нет, но вот выражение \%(a*a*a*\)\@>b не совпадет на строке aaaaaaaaac, и это выяснится быстро. А без атомарной группировки перебор будет куда более долгим. Впрочем, для коротких строк это не так существенно на быстрых компьютерах.
Но вот если текст длинный и у вас выражение со многими квантификаторами, то подумайте о применении атомарной группировки. Может сильно ускорить.
Вернемся к проверкам. В теории регулярных выражений их различают четыре: позитивные и негативные, опережающие и ретроспективные. Всё это совпадения нулевой ширины (якоря): в данной позиции они совпадают, если, соответственно: данное выражение совпадает/не совпадает после/до данной позиции.
Само выражение проверки в совпадение не входит в любом случае. Например, целая часть числа — это цифры, а потом должна идти точка. Но точка не является частью целой части числа.
В Вим есть все проверки. Некоторые можно реализовать и через описанный выше механизм оператора И, конечно. Более того, очень полезны якоря начала и конца совпадения \zs и \ze. Совпасть должно всё, но совпадение считается только от \zs до \ze. Например: \d\+\ze\.\d\+
Так, например, можно заменить точку на запятую в числах:
:s/\d\zs\.\ze\d/,/g
Так что именно проверки нужны нечасто. Но лучше о них знать.
Проверки — это \@=, \@!, \@<=, \@<! — опережающие позитивная и негативная и две ретроспективные. Эти символы применяются к символу перед ними (обычно к скобкам). Например, \%(\d\+\.\)\@<=\d\+ совпадет с дробной частью числа. Целая часть и точка должны быть, но в совпадение не входят.
Пример негативной проверки: if\s\+\%(then\)\@! — совпадет с if только в том случае, если после него не идет then.
Ретроспективные проверки допускают текст нефиксированной длины (в Перл — только фиксированной). Пример: \%(!.*\)\@<!text — совпадет с текстом text, который не внутри комментария (комментарий начинается с восклицательного знака). Это может быть трудоемко, особенно если у вас длинные строки или вы выходите за пределы строки. Можно ограничить длину проверяемого текста: \@42<= и \@42<!. Вместо 42 любое число, указывающее, сколько байт (не символов, а байт!) надлежит проверять.
Имейте в виду, что проверка является совпадением нулевой ширины: она проверила наличие/отсутствие совпадения, и всё. Если вы продолжаете выражение, оно пойдет опять по этому тексту, который только что проверялся! Пример:
\d\.\@=\d не совпадет на числе 4.2. Почему? Потому что класс \d требует цифру и она есть: 4. Далее идет проверка наличия точки: точка присутствует. Проверка совпала, пока всё идет хорошо. Далее опять класс \d, требующий цифру. Но в той позиции, где мы находимся (перед точкой), следующий символ отнюдь не цифра, а точка. Правильно так: \d\.\@=\.\d, но зачем тогда проверка?
Обычно регулярное выражение применяется к строке, что не всегда правильно для текста. В ТеХ, например, лучше писать предложение на строку и постараться не разрывать то, что потом будете искать. Например, Фоккера-Планка лучше оставить на одой строке!
Но за пределы строки легко выйти. К любому классу символов можно добавить _, чтобы в класс вошел символ конца строки. Так, \_. означает "любой символ, включая конец строки", \_w — словный символ или конец строки, \_s — пробел или конец строки, \_[а-яё] — русские буквы или конец строки, и т.п. Кстати, отрицание класса не распространяется на конец строки: \_[^1-9] означает "всё, кроме цифр 1-9, и ещё конец строки".
Можно привязаться к началу строки (\_^), к концу строки (\_$), к началу и концу файла (\%^ и \%$).
Можно даже привязаться к позиции курсора (\%# ), к метке под буквой q (\%'q), к строке, позиции в строке и виртуальной позиции (\%42l, \%42c, \%42v), и к выделенному тексту (\%V).
Это всё бывает полезно, например, для замены формулы в двойных долларах на окружение equation:
:%s/\$\$\(\_.\{-}\)\$\$/\\begin{equation}\1\\end{equation}/
Здесь мы использовали захват и ленивый квантификатор.
Помимо класса символов, из которых совпадает один любой, есть малоизвестная конструкция необязательных символов \%[]. Она совпадает всегда (как и любой квантификатор, допускающий нуль вхождений) и совпадает с любым числом перечисленных в нем символов: совпадение кончается на первом несовпавшем. Символы пробуются по порядку (в отличие от класса, где порядок роли не играет).
Пример: \<fu\%[nction]\>. Это совпадет с fu, fun, func, и т.п., вплоть до function. Якоря границ слова нужны, чтобы найти слово, а не кусок слова. А то мало ли, что начинается с fu!
Можно вместо символа указать класс: \%[[a-z]\s\d]
Все эти средства являются сложными, но полезными. Если вы научитесь их применять, то они могут очень пригодиться!
Например, вот в Wordlets играть.
Удачи, коллеги!