Регулярные выражения
В помощь:
- Конструктор регулярных выражений
- Регулярные выражения. Начало работы. RegExp
- Регулярные выражения на практике. Java RegEx
- Регулярные выражения от Джеффа Аваллоне
- Символы (основные) регулярных выражений на haskin.ru
- Regular Expression
Регулярные выражения с RegExp
Что такое регулярные выражения?
Давайте разберёмся, что же собой представляют регулярные выражения. Если вам когда-нибудь приходилось работать с командной строкой, вы, вероятно, использовали маски имён файлов. Например, чтобы удалить все файлы в текущей директории, которые начинаются с буквы "d", можно написать rm d*.
Регулярные выражения представляют собой похожий, но гораздо более сильный инструмент для поиска строк, проверки их на соответствие какому-либо шаблону и другой подобной работы. Англоязычное название этого инструмента - Regular Expressions или просто RegExp. Строго говоря, регулярные выражения - специальный язык для описания шаблонов строк.
Реализация этого инструмента различается в разных языках программирования, хоть и не сильно. В данной статье мы будем ориентироваться в 1-ю очередь на реализацию Perl Compatible Regular Expressions.
Основы синтаксиса
В 1-ю очередь стоит заметить, что любая строка сама по себе является регулярным выражением. Так, выражению Хаха, очевидно, будет соответствовать строка "Хаха" и только она. Регулярки являются регистрозависимыми, поэтому строка "хаха" (с маленькой буквы) уже не будет соответствовать выражению выше.
Однако уже здесь следует быть аккуратным - как и любой язык, регекспы имеют спецсимволы, которые нужно экранировать. Вот их список: . ^ $ * + ? { } [ ] \ | ( ). Экранирование осуществляется обычным способом - добавлением \ перед спецсимволом.
Набор символов
Предположим, мы хотим найти в тексте все междометия, обозначающие смех. Просто Хаха нам не подойдёт - ведь под него не попадут "Хехе", "Хохо" и "Хихи". Да и проблему с регистром первой буквы нужно как-то решить.
Здесь нам на помощь придут наборы - вместо указания конкретного символа, мы можем записать целый список, и если в исследуемой строке на указанном месте будет стоять любой из перечисленных символов, строка будет считаться подходящей. Наборы записываются в квадратных скобках - паттерну [abcd] будет соответствовать любой из символов "a", "b", "c" или "d".
Внутри набора большая часть спецсимволов не нуждается в экранировании, однако использование \ перед ними не будет считаться ошибкой. По прежнему необходимо экранировать символы "\" и "^", и, желательно, "]" (так, [][] обозначает любой из символов "]" или "[", тогда как [[]х] - исключительно последовательность "[х]"). Необычное на первый взгляд поведение регулярок с символом "]" на самом деле определяется известными правилами, но гораздо легче просто экранировать этот символ, чем их запоминать. Кроме этого, экранировать нужно символ "-", он используется для задания диапазонов (см. ниже).
Если сразу после [ записать символ ^, то набор приобретёт обратный смысл - подходящим будет считаться любой символ кроме указанных. Так, паттерну [^xyz] соответствует любой символ, кроме, собственно, "x", "y" или "z".
Итак, применяя данный инструмент к нашему случаю, если мы напишем [Хх][аоие]х[аоие], то каждая из строк "Хаха", "хехе", "хихи" и даже "Хохо" будут соответствовать шаблону.
Предопределённые классы символов
Для некоторых наборов, которые используются достаточно часто, существуют специальные шаблоны. Так, для описания любого пробельного символа (пробел, табуляция, перенос строки) используется \s, для цифр - \d, для символов латиницы, цифр и подчёркивания "_" - \w.
Если необходимо описать вообще любой символ, для этого используется точка (.). Если указанные классы написать с заглавной буквы (\S, \D, \W), то они поменяют свой смысл на противоположный - любой непробельный символ, любой символ, который не является цифрой, и любой символ кроме латиницы, цифр или подчёркивания соответственно.
Также с помощью регулярных выражений есть возможность проверить положение строки относительно остального текста. Выражение \b обозначает границу слова, \B - не границу слова, ^ - начало текста, а $ - конец. Так, по паттерну \bJava\b в строке "Java and JavaScript" найдутся первые 4 символа, а по паттерну \bJava\B - символы c 10-го по 13-й (в составе слова "JavaScript").
Диапазоны
У вас может возникнуть необходимость обозначить набор, в который входят буквы, например, от "б" до "ф". Вместо того, чтобы писать [бвгдежзиклмнопрстуф] можно воспользоваться механизмом диапазонов и написать [б-ф]. Так, паттерну x[0-8A-F][0-8A-F] соответствует строка "xA6", но не соответствует "xb9" (во-первых, из-за того, что в диапазоне указаны только заглавные буквы, во-вторых, из-за того, что 9 не входит в промежуток 0-8).
Механизм диапазонов особенно актуален для русского языка, ведь для него нет конструкции, аналогичной \w. Чтобы обозначить все буквы русского алфавита, можно использовать паттерн [а-яА-ЯёЁ]. Обратите внимание, что буква "ё" не включается в общий диапазон букв, и её нужно указывать отдельно.
Квантификаторы
Вернёмся к нашему примеру. Что, если в "смеющемся" междометии будет больше одной гласной между буквами "х", например "Хаахаааа"? Наша старая регулярка уже не сможет нам помочь. Здесь нам придётся воспользоваться квантификаторами.
Квантификатор | Число повторений | Пример | Подходящие строки |
---|---|---|---|
{n} | Ровно n раз | Ха{3}ха | Хаааха |
{m,n} | От m до n включительно | Ха{2,4}ха | Хаа, Хааа, Хааааха |
{m,} | Не менее m | Ха{2,}ха | Хааха, Хаааха, Хааааха и т.д. |
{,n} | Не более n | Ха{,3}ха | Хха, Хаха, Хааха, Хаааха |
Обратите внимание, что квантификатор применяется только к символу, который стоит перед ним.
Некоторые часто используемые конструкции получили в языке RegEx специальные обозначения:
Квантификатор | Аналог | Значения |
---|---|---|
? | {0,1} | 0 или 1 вхождение |
* | {0,} | 0 или более |
+ | {1,} | 1 или более |
Таким образом, с помощью квантификаторов мы можем улучшить наш шаблон для междометий до [Хх][аоеи]+х[аоеи]*, и он сможет распознавать строки "Хааха", "хееееех" и "Хихии".
Ленивая квантификация
Предположим, перед нами стоит задача - найти все HTML-теги в строке
<p><b>Яндекс</b> - русский <i>поисковик</i> для поиска!</p>
<p><b>Яндекс</b> - русский <i>поисковик</i> для поиска!</p>
Очевидное решение <.*> здесь не сработает - оно найдёт всю строку целиком, т.к. она начинается с тега абзаца и им же заканчивается. То есть содержимым тега будет считаться строка
p><b>Яндекс</b> - русский <i>поисковик</i> для поиска!</p
Это происходит из-за того, что по умолчанию квантификатор работают по т.н. жадному алгоритму - старается вернуть как можно более длинную строку, соответствующую условию. Решить проблему можно двумя способами. Первый - использовать выражение <[^>]*>, которое запретит считать содержимым тега правую угловую скобку. Второй - объявить квантификатор не жадным, а ленивым. Делается это с помощью добавления справа к квантификатору символа ?. Т.е. для поиска всех тегов выражение обратится в <.*?>.
Ревнивая квантификация
Иногда для увеличения скорости поиска (особенно в тех случаях, когда строка не соответствует регулярному выражению) можно использовать запрет алгоритму возвращаться к предыдущим шагам поиска для того, чтобы найти возможные соответствия для оставшейся части RegExp. Это называется ревнивой квантификацией. Квантификатор делается ревнивым с помощью добавления к нему справа символа +. Ещё одно применение ревнивой квантификации - исключение нежелательных совпадений. Так, паттерну ab*+a в строке "ababa" будут соответствовать только первые три символа, но не символы с третьего по пятый, т.к. символ "a", который стоит на третьей позиции, уже был использован для первого результата.
Чуть больше о жадном, сверхжадном и ленивом режимах квантификации вы сможете узнать из статьи о регулярных выражениях в Java.
Скобочные группы
Для нашего шаблона "смеющегося" междометия осталась самая малость - учесть, что буква "х" может встречаться более одного раза, например, "Хахахахааахахооо", а может и вовсе заканчиваться на букве "х". Вероятно, здесь нужно применить квантификатор для группы [аиое]+х, но если мы просто напишем [аиое]х+, то квантификатор + будет относиться только к символу "х", а не ко всему выражению. Чтобы это исправить, выражение нужно взять в круглые скобки: ([аиое]х)+.
Таким образом, наше выражение превращается в [Хх]([аиое]х?)+ - сначала идёт заглавная или строчная "х", а потом произвольное ненулевое количество гласных, которые (возможно, но не обязательно) перемежаются одиночными строчными "х". Однако это выражение решает проблему лишь частично - под это выражение попадут и такие строки, как, например, "хихахех" - кто-то может быть так и смеётся, но допущение весьма сомнительное. Очевидно, мы можем использовать набор из всех гласных лишь единожды, а потом должны как-то опираться на результат первого поиска. Но как?…
Запоминание результата поиска по группе
Оказывается, результат поиска по скобочной группе записывается в отдельную ячейку памяти, доступ к которой доступен для использования в последующих частях регэкспа. Возвращаясь к задаче с поиском HTML-тегов на странице, нам может понадобиться не только найти теги, но и узнать их название. В этом нам может помочь регулярное выражение <(.*?)>.
<p><b>Яндекс</b> - русский <i>поисковик</i> для поиска!</p>
Результат поиска по всему регексу: "<p>", "<b>", "</b>", "<i>", "</i>", "</p>".
Результат поиска по первой группе: "p", "b", "/b", "i", "/i", "/i", "/p".
На результат поиска по группе можно ссылаться с помощью выражения \n, где n - цифра от 1 до 9. Например выражению (\w)(\w)\1\2 соответствуют строки "aaaa", "abab", но не соответствует "aabb".
Если выражение берётся в скобки только для применения к ней квантификатора (не планируется запоминать результат поиска по этой группе), то сразу после первой скобки стоит добавить ?:, например (?:[abcd]+\w).
С использованием этого механизма мы можем переписать наше выражение к виду [Хх]([аоие])х?(?:\1х?)*.
Перечисление
Чтобы проверить, удовлетворяет ли строка хотя бы одному из шаблонов, можно воспользоваться аналогом булевого оператора OR, который записывается с помощью символа |. Так, под шаблон Анна|Одиночество попадают строки "Анна" и "Одиночество" соответственно. Особенно удобно использовать перечисления внутри скобочных групп. Так, например (?:a|b|c|d) полностью эквивалентно [abcd] (в данном случае второй вариант предпочтительнее в силу производительности и читаемости).
С помощью этого оператора мы сможем добавить к нашему регулярному выражению для поиска междометий возможность распознавать смех вида "Ахахаах" - единственной усмешке, которая начинается с гласной: [Хх]([аоие])х?(?:\1х?)*|[Аа]х?(?:ах?)+
Полезные сервисы
Потренироваться и/или проверить регулярное выражение на каком-либо тексте без написания кода можно с помощью таких сервисов, как RegExr, Regexpal или Regex101. Последний, вдобавок, приводит краткие пояснения к тому, как регулярка работает.
Разобраться, как работает регулярное выражение, которое попало к вам в руки, можно с помощью сервиса Regexper - он умеет строить понятные диаграмы по регуляркам.
RegExp Builder - визуальный конструктор функций JavaScript для работы с регулярными выражениями.
Лучшие статические анализаторы регулярных выражений.
Задания для закрепления
Найдите время
Время имеет формат часы:минуты. И часы, и минуты состоят из двух цифр, пример: 09:00. Напишите RegEx выражение для поиска времени в строке: "Завтрак в 09:00". Учтите, что "37:98" - некорректное время.
Решение
(2[0-3]|[0-1]\d):[0-5]\d
Java[^script]
Найдет ли регулярка Java[^script] что-нибудь в строке Java? А в строке JavaScript?
Решение
Ответы: нет, да.
В строке Java он ничего не найдёт, так как исключающие квадратные скобки в Java[^…] означают "один символ, кроме указанных". А после "Java" – конец строки, символов больше нет.
Да, найдёт. Поскольку регэксп регистрозависим, то под [^script] вполне подходит символ "S".
Цвет
Напишите регулярное выражение для поиска HTML-цвета, заданного как #ABCDEF, т.е. # и содержит затем 6 16-ричных символов.
Решение
Итак, нужно написать выражение для описания цвета, который начинается с "#", за которым следуют 6 16-ричных символов. 16-ричный символ можно описать с помощью [0-9a-fA-F]. Для его 6-кратного повторения мы будем использовать квантификатор {6}.
#[0-9a-fA-F]{6}
Разобрать арифметическое выражение
Арифметическое выражение состоит из 2 чисел и операции между ними, например:
1 + 2
1.2 *3.4
-3/ -6
-2-2
Список операций: "+", "-", "*" и "/".
Также могут присутствовать пробелы вокруг оператора и чисел.
Напишите регулярку, которая найдёт, как всё арифметическое действие, так и (через группы) 2 операнда
Решение
Регулярное выражение для числа, возможно, дробного и отрицательного: -?\d+(\.\d+)?.
Оператор – это [+*/\-]. Заметим, что дефис мы экранируем. Нам нужно число, затем оператор, затем число, и необязательные пробелы между ними. Чтобы получить результат в требуемом формате, добавим ?: к группам, поиск по которым нам не интересен (отдельно дробные части), а операнды наоборот заключим в скобки.
В итоге:
(-?\d+(?:\.\d+)?)\s*([-+*\/])\s*(-?\d+(?:\.\d+)?)
Символы (основные) регулярных выражений на haskin.ru
. - точка представляет 1 любой символ.
^ - начало строки.
$ - конец строки.
\s - пробел.
\S - не пробел.
\w - буква, цифра или подчёркивание _.
\d - любая цифра.
\D - любой символ, но не цифра.
[0-9] - любая цифра.
[a-z] - любая буква от a до z (весь латинский набор символов) в нижнем регистре.
[A-Z] - любая буква от a до z в ВЕРХНЕМ регистре.
[a-zA-Z] - любая буква от a до z в любом регистре.
[a-Z] - то же самое.
* - "повторитель" означает, что предшествующий символ может повторяться (0 или более раз).
.* - любой набор символов, например, условие <p> .* - найдет всё, что между тегами <p>.
(^.*$) - любой текст между началом и концом строки.
([0-9][0-9]*.) - ищет любые 2-значные в данном случае цифры.
\n\r - ищет пустые строки (если? оставить пустым окно "заменить", то удаляет пустые строки, но можно поставить \0).
^\s*$ - Ищет пустые строки содержащие пробел.
^[ ]*$ - Ищет пустые строки содержащие пробел.
Пример 1. Добавление знаков в начале и конце строки, абзаца
Допустим, необходимо некий текст, скопированный например из ворда, заключить в теги <p>
Ставим в "найти" - (^.*$) ищет соответственно начало и конец строки.
Ставим в "заменить на" - \1 и то, что нужно вставить в начало и конец строки, у нас в примере это <p>.
Выражение будет иметь следующий вид:<p>\1, как показано на рисунке 1 (далее нет нужды в рисунках, т.к. они будут идентичны этому с той лишь разницей, что выражения там будут другие.
P.S. Добавление круглых скобок () в поле "найти" обязательно, иначе будет убирать текст.
Добавление знаков в начале или в конце строки, абзаца
Допустим, необходимо некий текст из ворда заключить в одиночный тег <br>.
Для этого в поле "найти" вводим символ $ (конец строки), а в поле "заменить на" вводим нужный нам тег <br> не забывая добавить \1, т.е. выглядит это так: <br>\1 (в данном случае мы только добавляем нужный нам тег, поэтому круглые скобки не нужны, но если их поставить, то также будет заменять корректно).
Аналогично? можно добавлять что-нибудь и в начало строки, заменив в поле "найти" $ (конец строки) на символ ^ (начало) строки.
Удаление пустых строк
В поле "найти" вводим \n\r - ищет пустые строки, вроде как если оставить пустым окно "заменить", то удаляет пустые строки, но можно поставить \0. Далее открываем все файлы, которые надо обработать, вводим символы и жмём "заменить во всех открытых документах".
Результат: во всех открытых документах пустые строки, не содержащие пробелы, удалены.
Удаление пустых строк, содержащие пробелы
Работаем в 2 этапа: сначала удаляем пробелы, а потом пустые строки (см.п.1).
В поле "найти" вводим ^[ ]*$ или ^\s*$ - ищет пустые строки, содержащие пробел, аналогично, если? оставить пустым окно "заменить", то удаляет пробелы, но можно поставить \0. Далее открываем все файлы, которые надо обработать, вводим символы и жмём "заменить во всех открытых документах".
Результат: во всех открытых документах пустые строки очищены от пробелов. Далее для удаления этих строк выполняем действия указанные в п.1.
Как удалить всё между 2 словами?
Найти </description>([\s\S]*?)</offer>
Заменить на </description></offer>
[\s\S] - любой символ, включая переносы строк, табуляцию и т.д.;
*? - "не жадный" режим (минимальное подходящее совпадение);
Как в Notepad++ удалить все строки содержащие слово
В notepad++ в открытом файле нажимаем Ctrl+f, в поиске вводим нужное слово, ставим галочку в "помечать закладкой" и жмем "найти всё" (все строки, содержащие нужное слово, будут помечены). Далее в меню "поиск - закладки" выбрать пункт "удалить все строки с закладкой".
haskin.ru/vebmasteru/regulyarnye-vyrazheniya-v-notepad/#%D0%9A%D0%B0%D0%BA_%D0%B2_Notepad_%D1%83%D0%B4%D0%B0%D0%BB%D0%B8%D1%82%D1%8C_%D0%B2%D1%81%D0%B5_%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8_%D1%81%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B0%D1%89%D0%B8%D0%B5_%D1%81%D0%BB%D0%BE%D0%B2%D0%BE