Всем привет! Сегодня вводная заметка в регулярки: для тех, кто не очень в курсе, чтоб снять страх, ну и пара малоизвестных моментов, конечно.
Регэксп — это выражение, которое совпадает (или не совпадает) с текстом. В Вим поиск осуществляется слешем (/) или знаком вопроса (вверх по тексту) всегда по регэкспу. В простейшем случае регэксп — это и есть текст. Например, /this text
Про поиск и замену я уже рассказывал.
Однако часто вы не знаете точно, что за текст у вас будет. Например, вам надо найти число, но какое? Или просто может быть два пробела, а не один. Или табуляция вместо пробела. Или надо найти повторы слов. Или файлы *.gif Да мало ли что!
Для этого есть классы символов. Например, точка (.) совпадает с любым символом, \d с цифрой, \w с символом идентификатора, а \s — с любым пробельным символом. Можно задать класс явно через квадратные скобки: [а-я], [а-яё], [0-9-] (в последнем случае класс содержит цифры и минус, причем минус надо ставить в конце; во втором случае ё добавлена, так как она в Юникоде идет не по алфавиту и не попадает в диапазон [а-я]). Если после открывающей скобки стоит ^, то класс негативный: в него входит все, кроме перечисленного. Это удобно: [^0-9] означает "не цифра". Есть квантификаторы, повторители: * означает "нуль или более раз", а \+ — один или более раз. Так, /этот\s\s*текст совпадет при любом числе пробелов, от одного и более. То же можно записать чуть короче: /этот\s\+текст
Про классы символов будет отдельная заметка.
Еще есть якоря: ^ совпадает в начале строки, $ — в конце. Так, ^\s*$ означает строку, в которой ничего, кроме пробелов, нет. Визуально пустую строку. Якорей есть много, но о них в другой раз.
Наконец, захваты. В Виме это, по умолчанию, текст в \( ... \). То,что в них попало, доступно по номеру открывающей скобки как \1, \2 и так далее, до \9. Обычно хватает, если нет — пишите на Перле. Например, /\(\w\+\)\s*\1 — отловит повторы вроде the the или tomtom.
Регулярные выражения Вима отличаются и от перловских и от тех, что использует grep и другие утилиты. Основное различие связано со спецификой текстового редактора: как можно меньше символов должно иметь специальное значение. В Виме особый статус имеют:
- обратный слеш (\), который делает обычные символы особыми и особые — обычными. Так, \t обозначает табуляцию, а \. обозначает точку.
- точка (.), которая обозначает любой символ, кроме конца строки;
- звездочка (*), квантификатор "0 или более символов";
- квадратные скобки ([]) — множество символов;
- тильда (~), последнее выражение замены;
- крышка (^), начало строки;
- доллар ($), конец строки;
- слеш (/), как ограничитель.
Все остальное означает само себя. Это по умолчанию, с опцией magic.
Есть возможность отключить всю магию, поставив в начале \V — после этого все символы, кроме обратного слеша, означают сами себя. То есть возможности регулярных выражений все остаются — но через обратный слеш. Так, .* будет означать текст ".*", и всё, а если вам надо "любой символ нуль или более раз", то это после \V записывается \.\*
Можно и наоборот, сделать все символы особыми, кроме алфавитно-цифровых (и примкнувшего к ним подчерка _) — это \v. Тогда какое-нибудь сложное выражение вроде (.+)(){,} не будет вдвое длиннее из-за обилия слешей.
Есть еще две команды, \m и \M, не столь крайние, как \V и \v. Команда \m (magic) делает специальными доллар, точку, звездочку и тильду, помимо слешей, которые всегда специальные. Опция nomagic (\M) делает особыми намного меньше (якоря ^ и $, начало и конец строки). По умолчанию работает режим magic (его можно задать опцией magic). Лучше всего пользоваться регэкспами в режиме magic, включая \v в тех случаях, когда вам нужны сложные выражения с захватами, и \V тогда, когда вам наоборот, не нужны возможности регэкспов, но надо искать много особых символов. Например, надо найти ^int*.$ — пишите /\V^int*.$
Команды \C и \c позволяют сделать поиск регистро-зависимым или независимым, преодолевая флаг ignorecase. Символ \_. совпадает с любым символом, включая конец строки, для которого есть символ \n
Захваты доступны в выражении замены. Так, можно переставить местами фамилию и инициалы: s/\(\w\.\s*\w\.\)\s\+\(\w\w\+\)/\2, \1/gic
Давайте разберем эту команду. Команда замены :s///, между первым и вторым слешем выражением поиска, между вторым и третьим — выражением замены. После слеша могут идти модификаторы, у нас их три: g для замены всех вхождений, i для игнорирования регистра, c для подтверждения каждой замены. Выражение поиска имеет вид \(\w\.\s*\w\.\)\s\+ \(\w\w\+\) — в нем два захвата \(\). Первый ловит инициалы (мы ограничились английскими, но модификация для русских очевидна) вида "буква точка пробелы буква точка". Это записано как \w\.\s*\w\. После инициалов идет один или более пробелов, которые мы не зазватываем, а потом фамилия, которая состоит из трех и более английских букв. Если у вас могут быть фамилии с дефисом, пробелом и т.п., модифицируйте выражение. Инициалы, кстати, тоже бывают из двух букв и с чудесами. Выражение замены ставит первым второй захват (фамилию), потом запятую и пробел, затем инициалы.
На закуску рассмотрим несколько часто встречающихся регэкспов.
\w*\.\w* — типичный файл с расширением
\d\+\.*\d* — число; цифра, потом необязательная точка, потом возможны еще цифры. Да, выражение совпадет с 0...., но я не стремлюсь, чтобы оно совпало с числом и только с числом — мне надо просто найти числа в тексте.
([^)]*) — выражение в скобках, без вложенных скобок.
(\s*) — пустые скобки.
Кстати, пара подводных камней. Квантификаторы жадные — забирают как можно больше. Поэтому (.*) в строке (1-х)(1+х) совпадет со всем выражением: от первой ( до последней ). Или, например, выражение ".*," в строке "а, б, в" совпадет отнюдь не с "а,"
Второе: квантификатор *, "нуль или более", совпадает всегда. Поэтому не стоит заменять \d*\.*\d* ни на что, это выражение совпадет везде, в каждой позиции, потому что никакая его часть не является обязательной.
А вот как описать число, если оно может быть и 42, и .42, и 42., и 42.666, и чтобы совпадение было числом или хоть чем-нибудь, похожим на число (как 0...0 выше)... Это задачка для отдельной заметки. Но это будет для ценителей красоты, потому что на практике проще найти \d\+\.* и \.\d\+ по-отдельности.
Пожалуй, хватит для одной заметки. Про квантификаторы, захваты, якоря, классы символов, спецсимволы ветвления, модификаторы, замену и поиск будут отдельные заметки.