Поиск по сайту:

Как вы на самом деле используете регулярное выражение?


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

Синтаксис регулярных выражений, объяснение

Regex имеет репутацию ужасного синтаксиса, но его гораздо легче написать, чем прочитать. Например, вот обычное регулярное выражение для валидатора электронной почты, совместимого с RFC 5322:

(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[x01-
x08x0bx0cx0e-x1fx21x23-x5bx5d-x7f]|\[x01-x09x0bx0cx0e-x7f])*")
@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|[(?
:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-
9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[x01-x08x0bx0cx0e-x1fx21-x5ax53-x7f]|
\[x01-x09x0bx0cx0e-x7f])+)])

Если вам кажется, что кто-то ударился лицом о клавиатуру, вы не одиноки. Но под капотом весь этот беспорядок на самом деле программирует конечный автомат. Эта машина работает для каждого персонажа, пыхтя и сопоставляя на основе установленных вами правил. Множество онлайн-инструментов будут отображать схемы железных дорог, показывая, как работает ваша машина Regex. Вот то же регулярное выражение в визуальном виде:

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

Прежде всего: используйте отладчик регулярных выражений

Прежде чем мы начнем, если ваше регулярное выражение не очень короткое или вы не особенно опытны, вам следует использовать онлайн-отладчик при его написании и тестировании. Это значительно упрощает понимание синтаксиса. Мы рекомендуем Regex101 и RegExr, которые предлагают тестирование и встроенный справочник по синтаксису.

Как работает регулярное выражение?

А пока давайте сосредоточимся на чем-то гораздо более простом. Это диаграмма от Regulex для очень короткого (и определенно не совместимого с RFC 5322) регулярного выражения для сопоставления электронной почты:

Механизм Regex начинается слева и перемещается по строкам, сопоставляя символы по ходу. Группа № 1 соответствует любому символу, кроме разрыва строки, и будет продолжать соответствовать символам, пока следующий блок не найдет совпадение. В этом случае он останавливается, когда достигает символа @ , что означает, что группа № 1 фиксирует имя адреса электронной почты, а все, что после него, соответствует домену.

Регулярное выражение, которое определяет группу № 1 в нашем примере электронной почты:

(.+)

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

Точка — это символ «любого символа, кроме новой строки». Это соответствует всему в строке, поэтому, если вы передали этому регулярному выражению электронной почты адрес, например:

%$#^&%*#%$#^@gmail.com

Оно будет соответствовать %$#^&%*#%$#^ как имени, хотя это нелепо.

Символ «плюс» (+) — это управляющая структура, которая означает «соответствовать предыдущему символу или группе один или несколько раз». Это гарантирует совпадение всего имени, а не только первого символа. Это то, что создает петлю на схеме железной дороги.

Остальную часть Regex довольно просто расшифровать:

(.+)@(.+..+)

Первая группа останавливается, когда сталкивается с символом @ . Затем начинается следующая группа, которая снова соответствует нескольким символам, пока не достигнет символа точки.

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

Сопоставление символов

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

he+llo

Будет соответствовать слову «привет» с любым количеством букв «е». Любые другие символы должны быть экранированы для правильной работы.

Regex также имеет классы символов, которые действуют как сокращение для набора символов. Они могут различаться в зависимости от реализации Regex, но эти несколько являются стандартными:

  • . – соответствует чему угодно, кроме новой строки.
  • w – соответствует любому слову, включая цифры и символы подчеркивания.
  • d – соответствует числам.
  • b – соответствует пробельным символам (например, пробелу, табуляции, новой строке).

Все эти три имеют аналоги в верхнем регистре, которые инвертируют их функцию. Например, D соответствует всему, что не является числом.

Regex также имеет сопоставление наборов символов. Например:

[abc]

Будет соответствовать либо a, b, либо c. Это действует как один блок, а квадратные скобки — просто управляющие структуры. Кроме того, вы можете указать диапазон символов:

[a-c]

Или отмените набор, который будет соответствовать любому символу, которого нет в наборе:

[^a-c]

квантификаторы

Квантификаторы являются важной частью Regex. Они позволяют сопоставлять строки, в которых вы не знаете точный формат, но у вас есть неплохая идея.

Оператор + из примера с электронной почтой – это квантификатор, а именно квантификатор один или несколько. Если мы не знаем длины определенной строки, но знаем, что она состоит из буквенно-цифровых символов (и не пуста), мы можем написать:

w+

В дополнение к + также есть:

  • Оператор *, который соответствует «ноль или более». По существу то же самое, что и +, за исключением того, что есть возможность не находить совпадения.
  • Оператор ?, который соответствует «нулю или единице». Это делает символ необязательным; он либо есть, либо его нет, и он не будет совпадать более одного раза.
  • Числовые квантификаторы. Это может быть одно число, например {3}, что означает «ровно 3 раза», или диапазон, например {3-6}. Вы можете пропустить второе число, чтобы сделать его неограниченным. Например, {3, означает 3 или более раз. Как ни странно, вы не можете пропустить первое число, поэтому, если вы хотите «3 или меньше раз», вам придется использовать диапазон.

Жадные и ленивые квантификаторы

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

Вот пример: скажем, вы пытаетесь сопоставить HTML или что-то еще с закрывающими фигурными скобками. Ваш входной текст:

<div>Hello World</div>

И вы хотите сопоставить все в скобках. Вы можете написать что-то вроде:

<.*>

Это правильная идея, но она неверна по одной важной причине: механизм регулярных выражений сопоставляет «div>Hello World

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

Решение состоит в том, чтобы сделать наш квантификатор ленивым, что означает, что он будет соответствовать как можно меньшему количеству символов. Под капотом это на самом деле будет соответствовать только одному символу, а затем расширяться, чтобы заполнить пространство до следующего совпадения блока, что делает его намного более производительным в больших операциях Regex.

Сделать квантификатор ленивым можно путем добавления вопросительного знака сразу после квантификатора. Это немного сбивает с толку, потому что ? уже является квантификатором (и на самом деле жадным по умолчанию). Для нашего примера HTML регулярное выражение исправлено с помощью этого простого добавления:

<.*?>

Ленивый оператор можно прикрепить к любому квантификатору, включая +?, {0,3}? и даже ??. Хотя последний не имеет никакого эффекта; поскольку вы все равно сопоставляете ноль или один символ, расширяться некуда.

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

Группы в Regex имеют множество целей. На базовом уровне они объединяют несколько токенов в один блок. Например, вы можете создать группу, а затем использовать квантификатор для всей группы:

ba(na)+

Это группирует повторяющиеся «на» в соответствии с фразой banana, banananana и т. д. Без группы движок регулярных выражений просто снова и снова сопоставлял бы конечный символ.

Этот тип группы с двумя простыми круглыми скобками называется группой захвата и будет включать ее в вывод:

Если вы хотите избежать этого и просто сгруппировать токены вместе для целей выполнения, вы можете использовать группу без захвата:

ba(?:na)

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

Вы также можете назвать свои группы для удобства при работе с выводом:

(?'group')

Вы можете ссылаться на них в своем регулярном выражении, что заставляет их работать аналогично переменным. Вы можете ссылаться на неименованные группы с помощью токена 1, но это происходит только до 7, после чего вам нужно будет начать именовать группы. Синтаксис для ссылки на именованные группы:

k{group}

Это ссылка на результаты именованной группы, которая может быть динамической. По сути, он проверяет, встречается ли группа несколько раз, но не заботится о позиции. Например, это можно использовать для сопоставления всего текста между тремя одинаковыми словами:

В классе group вы найдете большую часть управляющей структуры Regex, включая просмотр вперед. Упреждающие проверки гарантируют, что выражение должно совпадать, но не включают его в результат. В некотором смысле он похож на оператор if и не будет соответствовать, если вернет false.

Синтаксис положительного просмотра вперед: (?=). Вот пример:

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

Помимо положительных прогнозов, есть также:

  • (?!) – отрицательный просмотр вперед, который гарантирует, что выражение не совпадает.
  • (?<=) – позитивный просмотр назад, который не везде поддерживается из-за некоторых технических ограничений. Они помещаются перед выражением, которое вы хотите сопоставить, и они должны иметь фиксированную ширину (т. е. без квантификаторов, кроме {number}). В этом примере вы можете использовать (?<=@ )w+.w+ , чтобы соответствовать доменной части электронного письма.
  • (? – отрицательный просмотр назад, который аналогичен положительному просмотру назад, но инвертирован.

Различия между двигателями регулярных выражений

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

Например, версии sed, скомпилированные для macOS и FreeBSD, не поддерживают использование t для представления символа табуляции. Вы должны вручную скопировать символ табуляции и вставить его в терминал, чтобы использовать табуляцию в командной строке sed.

Большая часть этого руководства совместима с PCRE, механизмом регулярных выражений по умолчанию, используемым для PHP. Но механизм регулярных выражений JavaScript отличается — он не поддерживает именованные группы захвата с кавычками (ему нужны скобки) и, среди прочего, не может выполнять рекурсию. Даже PCRE не полностью совместим с разными версиями и имеет много отличий от регулярных выражений Perl.

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

Как запустить регулярное выражение

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

Обычно это принимает формат:

/match/g

Все, что находится внутри косой черты, совпадает с нашим. g – это модификатор режима. В этом случае он говорит движку не прекращать работу после того, как найдет первое совпадение. Для поиска и замены регулярного выражения вам часто придется форматировать его следующим образом:

/find/replace/g

Это заменяет все во всем файле. Вы можете использовать ссылки на группы захвата при замене, что делает Regex очень хорошим форматом текста. Например, это регулярное выражение будет соответствовать любым тегам HTML и заменит стандартные скобки квадратными скобками:

/<(.+?)>/[1]/g

Когда это запустится, движок будет сопоставлять <div> и

, что позволит вам заменить этот текст (и только этот текст). Как видите, внутренний HTML не затрагивается:

Это делает Regex очень полезным для поиска и замены текста. Для этого используется утилита командной строки sed, которая использует основной формат:

sed '/find/replace/g' file > file

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

Regex также поддерживается во многих текстовых редакторах и может значительно ускорить рабочий процесс при выполнении пакетных операций. Vim, Atom и VS Code имеют встроенный поиск и замену Regex.

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

Например, регулярное выражение в JavaScript может быть создано буквально или динамически с использованием глобального объекта RegExp:

var re = new RegExp('abc')

Это можно использовать напрямую, вызвав метод .exec() вновь созданного объекта регулярного выражения или используя .replace(), .match() и .matchAll() для строк.