(Не бойтесь) Regexs: практическое руководство по регулярным выражениям

0
4768
views
Изучаем регулярные выражения

Перевод статьи Джоша Хокинса «(Don’t Fear) The Regex: A Practical Introduction to Regular Expressions».

Вам уже приходилось работать со строками? Верно, именно с теми «массивами символов», которые мы все знаем и любим. Если только вы не пишете исключительно на С, я готов поспорить, что со строками вы встречались и, может быть, даже очень часто.

Но как насчет использования множества строк? Или использования строк, которые генерировала не ваша программа? Возможно, вам нужно выбрать все аргументы командной строки из email. Или вы читаете инструкции, написанные для людей, и вам нужен какой-то более структурированный способ, чтоб с этим справиться.

Конечно, можно делать итерации с каждым словом или элементом во всех этих строках. Вероятно, вы даже можете представить, какой код вы бы использовали для этого. Но в больших приложениях это может стать слишком дорогостоящей задачей.

Что такое регулярные выражения

Давайте быстро (и не слишком углубляясь в информатику) определимся с тем, что такое регулярные выражения.

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

Учимся писать регулярные выражения

Регулярное выражение ограничивается слэшами – //. Строка совпадает с регулярным выражением, если она совпадает с шаблоном между двумя косыми чертами. Например, «Привет» совпадает с /Привет/. Поэтому мы можем использовать данное регулярное выражение для поиска слова «Привет» в строке.

Регулярным выражением может быть строка (набор символов). Введенная обычным образом. Например, /Hello World/ будет соответствовать строке «Hello World».

Если нам нужно найти любое слово (имеется в виду отдельное слово, состоящее только из букв), мы можем упростить поиск, применив немного магии regex: \w будет соответствовать любому одному слову в строке (w – word – «слово»).

Аналогичный подход можно применять к цифрам: \d.

Пример 1

То, что мы можем посмотреть, соответствуют ли какие-то элементы строки заданному шаблону, это, конечно, хорошо. Но где это может быть полезно?

Допустим, мы написали чат-бота, который отслеживает упоминание в разговоре имени «Джош» (имя автора статьи, – прим. перев.). В общем, наш бот сканирует каждое сообщение в чате, пока не найдет соответствие. Если найдет, то отсылает сообщение: «Ох, я надеюсь, вы не говорите плохо о моем приятеле Джоше!».

Для поиска соответствий наш бот будет использовать шаблон /Джош/.

Внезапно в чате появляется сообщение: «Эли: Джош, тебе в самом деле нужно так много кофеина?».

Наш бот сканирует это сообщение и находит совпадение с шаблоном! Он отсылает свой стандартный ответ, пугая Эли. Миссия выполнена!

Или нет? Что, если бы наш робот был немного смышленее? Что, если бы он обращался к человеку пои мени? Например, писал бы: « Ох, я надеюсь, вы не говорите плохо о моем приятеле Джоше, Эли!».

Это возможно. Но для начала нам нужно кое-что изучить. Начнем с квантификаторов.

Регулярные выражения помогут найти иголку в стогу сена
Регулярные выражения помогут найти иголку в стогу сена.

Квантификаторы (указатели количества вхождений символов)

0 или много

Символ «звездочка» – * – означает любое количество (в том числе нулевое) вхождений символа, после которого эта звездочка стоит. Например, /a*/ будет соответствовать «aaaaa», а также «». Верно, это может быть и пустая строка.

Звездочка служит для обозначения чего-то опционального, потому что обозначаемый ею символ может не существовать. А может и существовать. И «существовать» много, много раз (теоретически – бесконечное количество раз).

Регулярное выражение /Джош/ дает нам совпадение с «Джош». А выражение /Д*жош/ будет совпадать также с «ДДДДДДДжош» и «жош».

1 или много

Знак «плюс» – + – означает любое количество вхождений символа, после которого этот плюс стоит, но не менее одного вхождения. Он работает так же, как и «звездочка», за исключением того, что нулевое вхождение символа здесь невозможно. Для совпадения должно быть хотя бы одно вхождение.

Таким образом, выражению /Д+жош/ будет соответствовать «Джош» и «ДДДДДДДжош», но не «жош».

Шаблоны (специальные символы)

Отлично, теперь мы можем находить больше интересных вещей. Может, в этом чате кто-нибудь будет сильно зол на меня и будет кричать «Джоооооош!»…

А что если этот «кто-то» будет аж так зол, что прямо будет головой об клавиатуру биться? Как нам выловить в чате «лавыофылджвоа», если мы не знаем, на какую клавишу пришелся его нос?

С помощью шаблонов!

Шаблоны могут заменять собой буквально любой символ (или даже много символов). Синтаксически это обозначается точкой. Да, просто точкой. Не путайте с точкой в конце предложения.

Мы можем использовать этот шаблон поиска в комбинации с уже известными нам квантификаторами: /Дж+.*ош/. Давайте разберем. Здесь у нас будет одно вхождение «Д», одно или больше вхождение «ж», ноль или много любых символов (.*) и одно вхождение «ош». Можно проследить, что здесь у нас определенные группы.

Группировка символов

Группы символов это блоки символов, по порядку располагающиеся в строке. Когда вы используете * или +, это указывает на количество вхождений не просто последнего символа, после которого стоит квантификатор, а последней группы символов.

Группы полезны сами по себе, но они могут стать очень мощным инструментом в сочетании с повторяющимися символами. Определить группу символов можно с помощью круглых скобок (вот этих).

Например, мы хотим повторить «Джо», но не «ш». Чтобы шаблону соответствовало «ДжоДжоДжоДжоДжош». Сделать это можно с помощью регулярного выражения /(Джо)+ш/. Просто, правда?

И наконец, давайте вернемся к нашему примеру с чат-ботом. Как нам «выловить» из строки имя Эли, чтобы бот мог обратиться именно к ней?

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

Для этого обычно вам нужно будет использовать что-то вроде \1 – это соответствует первой определенной группе.

Возьмем для примера регулярное выражение /(.+) \1/. Здесь мы видим группу случайных символов, которая встречается один раз или больше, после которой идет пробел, а затем идет повтор тех же символов. Таким образом, с этим шаблоном будет совпадать «абв абв», но не «абв где», хотя сама по себе группа символов «где» совпадает с шаблоном (.+).

Возможно, это одна из самых полезных функций регулярных выражений в программировании.

Поиск совпадений

Пример 2

Ффухх! Давайте теперь продолжим нашу историю с чат-ботом и используем то, чему мы научились, чтобы определить, кто вспоминает Джоша.

Сообщение в чате выглядело так: «Эли: Джош, тебе в самом деле нужно так много кофеина?».

Если мы хотим «выловить» имя отправителя сообщения, наше регулярное выражение может выглядеть следующим образом:

/(\w+): .*Джош.*/.

В этом выражении мы видим:

  • (\w+): – группу из одного или больше слов, за которой следует двоеточие,
  • .* – любые символы, встречающиеся любое количество раз, включая ноль,
  • строку «Джош», за которой опять следуют
  • .* – любые символы, встречающиеся любое количество раз, включая ноль.

Памятка: регулярное выражение /.*слово.*/ это простой способ найти вхождения строки «слово», по бокам которой могут быть, а могут и не быть любые другие слова и символы.

В Python это регулярное выражение может выглядеть так:

[python]import re
pattern = re.compile(ur'(\w+): .*Josh.*’) # Our regex
string = u"Eli: Josh go move your laundry" # Our string

matches = re.match(pattern, string) # Test the string

who = matches.group(1) # Get who said the message
print(who) # "Eli"
[/python]

Обратите внимание: мы использовали .group(1) так же, как ранее использовали \1 в шаблоне регулярного выражения. Здесь нет ничего нового за исключением того, что это использование regex именно в Python.

Начало и конец

До сих пор мы искали вхождения нужной последовательности символов в любой части строки. Например, /(Джо)+ш/ совпадет с любым вхождением «Джо-повторы-ш» где угодно в строке.

Но что если нас интересуют только строки, начинающиеся с «Джо-повторы-ш»? Это можно выразить шаблоном /^(Джо)+ш/, где ^ означает начало строки.

Аналогично, $ может использоваться для обозначения конца строки.

Таким образом, если нам нужны только строки, состоящие исключительно из «Джо-повторы-ш» (без других слов), то регулярное выражение будет выглядеть так: /^(Джо)+ш$/.

Перечисление символов

Допустим, вы пишете regex, напоминающий сандвич. Вы не знаете, захочет ли клиент пшеничный хлеб или ржаной, вам любой подойдет. Как добавить возможность выбора в регулярное выражение? С помощью перечисления символов!

Перечисления символов позволяют определить набор возможных значений для группы. В случае с нашим сандвичем синтаксис будет такой: (пшеничный|ржаной). Вертикальной чертой разделяются два допустимых варианта. Это означает, что и «пшеничный», и «ржаной» будет приемлемым совпадением.

Для перечисления вариантов можно также использовать квадратные [скобки]. Здесь возможным вариантом будет каждый из символов «с», «к», «о», «б», «к», «и» по отдельности. Это может быть удобным для более сложных группировок символов, поскольку позволяет заменить какой-нибудь символ более сложным выражением внутри группы символов.

Модификаторы

Модификаторы (флаги)

До сих пор мы говорили о шаблоне внутри /слэшей/, верно? Но что располагается за их пределами?

Слева – ничего интересного. А вот справа может быть кое-что очень полезное. Просто позор, что до сих пор мы это игнорировали!

Модификаторы изменяют правила применения регулярных выражений.

Вот список самых распространенных модификаторов (с Regex101.com):

Модификатор Название Описание
g global – «глобальный» Все вхождения (не возвращать первое же вхождение).
m multi-line – «многострочный» Текст вхождения воспринимается как многострочный; метасимволы ^ и $ обозначают начало и конец каждой отдельной строки, а не только всего текста.
i insensitive – «нечувствительность» Игнорирование регистра [a-zA-Z].
x extended – «расширенный» Игнорируются пробелы и текст после # в шаблоне.
X extra – «добавочный» Любой неспециальный символ после обратного слэша \ приводит к ошибке.
s single line – «одна строка» Текст вхождения считается одной строкой. Шаблон «точка» включает и символы новой строки.
u unicode Строки шаблона воспринимаются как UTF-16. Также заставляет управляющие последовательности соответствовать символам Юникода. Полезно для поддержки кириллических символов в регулярном выражении.
U ungreedy – инвертация жадности Совпадения становятся ленивыми по умолчанию. Теперь знак вопроса после квантификатора делает его жадным.
A anchored – «закрепленный» Шаблон привязывается к началу строки.
J duplicate – «дубликат» Разрешает одинаковые имена для подшаблонов.

Например, до сих пор все наши примеры были чувствительными к регистру. Это значит, что изменение регистра любого символа приведет к несовпадению с шаблоном. Но добавив модификатор i, мы можем сделать наш шаблон нечувствительным к регистру.

Возможно, Эли была настолько злая, что решила поспамить в чате с помощью СМеШЕНия реГисТрОВ. Не стоит бояться, у нас же есть i! Мы можем найти вхождение строки «Я нЕеенавижу ЖиТь С ДЖОШем!!» с помощью регулярного выражения /я не+навижу жить с джошем!+/i. Теперь это будет понятнее и полезнее. Прекрасно!

Остальные модификаторы я оставлю вам для самостоятельного разбора, замечу лишь, что в целом чаще всего используется igm.

Что дальше?

Надеюсь эта статья продемонстрировала вам, как можно более изощренно взаимодействовать со строками. Конечно, это лишь прикосновение к regexs, но вы уже сможете составить регулярное выражение для каких-то простых задач.

В регулярных выражениях используется множество символов / токенов. Скорее всего вы будете натыкаться на них на Stack Overflow. Иногда их значение можно угадать, исходя из своего предыдущего опыта (например, \n это символ новой строки). Но в любом случае, остается еще много для изучения.

Просмотреть полный список токенов и протестировать свое регулярное выражение можно здесь. Я все еще пользуюсь этим сайтом при написании regexs, потому что у них очень полезный и мощный инструмент тестирования. Он даже может сгенерировать для вас код, если вы еще не уверены, как это делать в вашем языке программирования.

А если все написанное здесь для вас легкотня, обратите внимание на regex-кроссворды. Уж они заставят вас мыслить регулярными выражениями!

ОСТАВЬТЕ ОТВЕТ

Please enter your comment!
Please enter your name here