Вопрос или проблема
Я написал регулярное выражение, которое отлично работает в определенной программе (grep, sed, awk, perl, python, ruby, ksh, bash, zsh, find, emacs, vi, vim, gedit, …). Но когда я использую его в другой программе (или в другой версии Unix), оно перестает совпадать. Почему?
К сожалению, по историческим причинам разные инструменты имеют немного разный синтаксис регулярных выражений, и иногда некоторые реализации имеют расширения, которые не поддерживаются другими инструментами. Хотя существует общая база, кажется, что каждый разработчик инструментов сделал немного другие выборы.
Это приводит к тому, что если у вас есть регулярное выражение, которое работает в одном инструменте, вам, возможно, потребуется его изменить, чтобы оно работало в другом инструменте. Основные различия между общими инструментами такие:
- требуется ли обратная косая черта перед операторами
+?|(){}
; - какие расширения поддерживаются кроме базовых
.[]*^$
и обычно+?|()
В этом ответе я перечисляю основные стандарты. Изучите документацию инструментов, которые вы используете, для получения деталей.
Сравнение движков регулярных выражений в Википедии содержит таблицу с перечнем функций, поддерживаемых распространенными реализациями.
Базовые регулярные выражения (BRE)
Базовые регулярные выражения кодифицированы стандартом POSIX. Это синтаксис, используемый в grep
, sed
и vi
. Этот синтаксис предоставляет следующие возможности:
^
и$
совпадают только в начале и конце строки..
соответствует любому символу (или любому символу, кроме новой строки).[…]
совпадает с любым одним символом, указанным в скобках (набор символов). Если первым символом после открывающей скобки является^
, совпадают символы, которые не указаны. Чтобы включить]
, поместите его сразу после открывающей[
(или после[^
, если это отрицательный набор). Если-
находится между двумя символами, это обозначает диапазон; чтобы включить буквальный-
, поместите его туда, где он не может быть разобран как диапазон.- Обратная косая черта перед любым из
^$.*\[
экранирует следующий символ. *
соответствует предыдущему символу или подвыражению 0, 1 или более раз.\(…\)
является синтаксической группой, для использования с оператором*
или обратными ссылками и заменами\DIGIT
.- Обратные ссылки
\1
,\2
, … совпадают с текстом, совпавшим с соответствующей группой, например\(fo*\)\(ba*\)\1
совпадает сfoobaafoo
, но не сfoobaafo
. Нет стандартного способа обратиться к 10-й группе и далее (стандартное значение\10
представляет первую группу, за которой следует0
).
Следующие функции также являются стандартными, но отсутствуют в некоторых ограниченных реализациях:
\{m,n\}
совпадает с предыдущим символом или подвыражением от m до n раз; n или m могут быть опущены, и\{m\}
означает ровно m.- Внутри скобок можно использовать классы символов, например
[[:alpha:]]
совпадает с любой буквой. Современные реализации скобочных выражений также включают сопоставления и элементы эквивалентности, такие как[.ll.]
и классы эквивалентности, такие как[=a=]
.
Следующие элементы являются обычными расширениями (особенно в инструментах GNU), но они не встречаются во всех реализациях. Проверьте руководство инструмента, который вы используете.
\|
для альтернативы:foo\|bar
совпадает сfoo
илиbar
.\?
(короткое для\{0,1\}
) и\+
(короткое для\{1,\}
) совпадают с предыдущим символом или подвыражением не более 1 раза или не менее 1 раза соответственно.\n
совпадает с новой строкой,\t
совпадает с табуляцией и т.д.\w
совпадает с любым словом-составляющей (короткое для[_[:alnum:]]
, но с вариациями в зависимости от локализации) и\W
совпадает с любым символом, который не является словом-составляющей.\<
и\>
совпадают с пустой строкой только в начале или конце слова соответственно;\b
совпадает с обоими случаями, а\B
совпадает, когда\b
не совпадает.
Обратите внимание, что инструменты без оператора \|
не обладают полной мощностью регулярных выражений. Обратные ссылки позволяют делать несколько дополнительных вещей, которые невозможно реализовать с помощью регулярных выражений в математическом смысле.
Расширенные регулярные выражения (ERE)
Расширенные регулярные выражения кодифицированы стандартом POSIX. Их основное преимущество перед BRE – это регулярность: все стандартные операторы являются простыми знаками препинания, косая черта перед знаком препинания всегда экранирует его. Это синтаксис, использующийся в awk
, grep -E
или egrep
, BSD (и GNU и вскоре POSIX) sed -E
(ранее sed -r
в GNU sed
), и оператору bash / ksh93 / yash / zsh¹ =~
. Этот синтаксис предоставляет следующие функции:
^
и$
совпадают только в начале и конце строки..
соответствует любому символу (или любому символу, кроме новой строки).[…]
соответствует любому одному символу, указанному в скобках (набор символов). Отрицание с начальным^
и диапазоны работают, как в BRE (см. выше). Классы символов могут быть использованы, но отсутствуют в некоторых реализациях. Современные реализации также поддерживают классы эквивалентности и элементы сопоставления. Косая черта в скобках экранирует следующий символ в некоторых, но не во всех реализациях; используйте\\
для обозначения косой черты для переносимости.(…)
является синтаксической группой для использования с*
или заменами\DIGIT
.|
для альтернативы:foo|bar
совпадает сfoo
илиbar
.*
,+
и?
совпадают с предыдущим символом или подвыражением определенное количество раз: 0 или более для*
, 1 или более для+
, 0 или 1 для?
.- Косая черта экранирует следующий символ, если он не является буквенно-цифровым.
{m,n}
совпадает с предыдущим символом или подвыражением от m до n раз (отсутствует в некоторых реализациях); n или m могут быть опущены, и{m}
означает ровно m.- Некоторые обычные расширения, как в BRE: обратные ссылки
\DIGIT
(особенно отсутствуют в awk, кроме реализации busybox, где можно использовать$0 ~ "(...)\\1"
); специальные символы\n
,\t
и т.д.; границы слов\b
и\B
, составные части слов\b
и\B
и т.д.
PCRE (Perl-совместимые регулярные выражения)
PCRE является расширением ERE, первоначально введенным в Perl и принятым в GNU grep -P
и многих современных инструментах и языках программирования, обычно через библиотеку PCRE. Подробное форматирование с примерами можно найти в документации Perl. Не все функции последней версии Perl поддерживаются PCRE (например, выполнение кода Perl поддерживает только Perl). Смотрите руководство PCRE для краткого изложения поддерживаемых функций. Основные дополнения к ERE:
(?:…)
является негруппирующей группой: как(…)
, но не учитывается при обратных ссылках.(?=FOO)BAR
(позитивный просмотр вперед) совпадает сBAR
, но только если также есть совпадение дляFOO
, начиная с той же позиции. Это наиболее полезно для якорения совпадения без включения следующего текста:foo(?=bar)
совпадает сfoo
, но только если за ним следуетbar
.(?!FOO)BAR
(негативный просмотр вперед) совпадает сBAR
, но не если также есть совпадение дляFOO
на той же позиции. Например,(?!foo)[a-z]+
совпадает с любым строчным словом, которое не начинается сfoo
;[a-z]+(?![0-9)
совпадает с любым строчным словом, за которым не следует цифра (так что вfoo123
, это соответствуетfo
, но неfoo
).(?<=FOO)BAR
(позитивный просмотр назад) совпадает сBAR
, но только если он непосредственно предшествован совпадением дляFOO
.FOO
должно иметь известную длину (нельзя использовать операторы повторения, такие как*
). Это наиболее полезно для якорения совпадения без включения предыдущего текста в совпадение:(?<=^| )foo
совпадает сfoo
, но только если перед ним есть пробел или начало строки.(?<!FOO)BAR
(негативный просмотр назад) совпадает сBAR
, но только если его непосредственно не предшествует совпадение дляFOO
.FOO
должно иметь известную длину (нельзя использовать операторы повторения, такие как*
). Это наиболее полезно для якорения совпадения без включения предыдущего текста в совпадение:(?<![a-z])foo
совпадает сfoo
, но только если перед ним не стоит строчная буква.
Emacs
Синтаксис Emacs является промежуточным между BRE и ERE. В дополнение к Emacs он является стандартным синтаксисом для -regex
в GNU find. Emacs предлагает следующие операторы:
^
,$
,.
,[…]
,*
,+
,?
, как в ERE\(…\)
,\|
,\{…\}
,\DIGIT
, как в BRE- больше последовательностей с обратной косой чертой;
\<
и\>
для границ слов; и больше в последних версиях Emacs, которые часто не поддерживаются другими движками с синтаксисом, похожим на Emacs.
Shell глобы
Shell глобы (подстановочные знаки) выполняют сопоставление шаблонов, используя синтаксис, который полностью отличается от регулярных выражений и менее мощный. В дополнение к оболочкам эти подстановочные знаки доступны с другими инструментами, такими как find -name
и фильтры rsync. POSIX-шаблоны включают следующие функции:
?
совпадает с любым одним символом.[…]
– это набор символов, как в общих синтаксисах регулярных выражений. Некоторые оболочки не поддерживают классы символов. Некоторые оболочки требуют использования!
вместо^
для отрицания набора.*
совпадает с любой последовательностью символов (часто кроме/
при сопоставлении путей файлов; если/
исключен из*
, то**
иногда включает/
, но проверьте документацию инструмента).- Обратная косая черта экранирует следующий символ.
Ksh предлагает дополнительные функции, которые делают его сопоставление шаблонов равносильным по мощности регулярным выражениям. Эти возможности также доступны в bash после выполнения shopt -s extglob
. Zsh имеет другой синтаксис, но может также поддерживать синтаксис ksh после setopt ksh_glob
.
¹ если только опция rematchpcre
не включена в zsh
, в этом случае =~
использует PCRE. Расширенные регулярные выражения ksh93 также поддерживают некоторые расширенные операторы Perl, такие как операторы просмотра.
Ответ или решение
Причина, по которой регулярное выражение работает в одной программе, но не работает в другой, может быть связана с различиями в синтаксисе и поддерживаемых функциях различных реализаций регулярных выражений. Чтобы понять это явление, полезно обратиться к теории, примерам и применению, что позволит глубже осознать происхождение этих различий и как с ними работать.
Теоретическая основа
Регулярные выражения (regex) представляют собой мощный инструмент для поиска и работы с текстом на основе заданных шаблонов. Однако, из-за исторических причин, в различных инструментах и языках программирования существуют разные реализации и синтаксисы. Это может привести к проблемам совместимости между ними. Основные стандарты включают:
-
POSIX Basic Regular Expressions (BRE): BRE – это базовый стандарт для регулярных выражений, обеспечивая базовые операции, такие как привязка к началу (^) и концу ($) строки, одиночный символ (.) и квантификатор (*).
-
POSIX Extended Regular Expressions (ERE): ERE расширяет BRE, добавляя поддержку операторов, таких как +, ? и |, без необходимости экранирования.
-
Perl-Compatible Regular Expressions (PCRE): Это расширение синтаксиса ERE, добавляющее такие функции, как ненулевые группы (?:…), положительные и отрицательные зацепы (?=…), (?!…), (?<=…), (?<!…), которые значительно расширяют возможности регулярных выражений.
Различные инструменты могут поддерживать эти стандарты по-разному. Например, инструмент grep
может работать с разными вариантами регулярных выражений в зависимости от флага (-E для ERE или -P для PCRE). В то же время языки программирования, такие как Python или Ruby, используют свои библиотеки для реализации регулярных выражений, которые часто основываются на PCRE, но могут включать дополнительные особенности или ограничения.
Примеры различий в синтаксисе
Рассмотрим несколько примеров того, как различия в синтаксисе могут повлиять на работу регулярного выражения:
-
Квантификаторы и экранирование: В BRE, символы +, ?, | и {} требуют экранирования, тогда как в ERE они работают без экранирования. Например:
- BRE:
a\+
соответствует одной или более буквам "a". - ERE:
a+
соответствует одной или более буквам "a".
- BRE:
-
Альтернация: В BRE используется экранированный символ
\|
, тогда как в ERE –|
. Пример:- BRE:
foo\|bar
соответствует либо "foo", либо "bar". - ERE:
foo|bar
соответствует либо "foo", либо "bar".
- BRE:
-
Негативный и позитивный зацеп: PCRE позволяет использовать зацепы, которых нет в BRE или ERE. Например:
- PCRE:
(?<!foo)bar
соответствует "bar", если перед ним не стоит "foo".
- PCRE:
Применение и советы
Для работы с регулярными выражениями в разных системах и инструментах вам может понадобиться адаптировать ваши выражения. Вот несколько шагов, которые помогут в этом:
-
Изучение документации: Внимательно изучите документацию к инструменту или языку, который вы используете, чтобы понять, какой синтаксис регулярных выражений поддерживается. Часто можно найти таблицы сравнения, которые указывают на поддерживаемые функции и особенности.
-
Тестирование регулярных выражений: Прежде чем использовать регулярное выражение в боевых условиях, протестируйте его в безопасной среде. Это поможет выявить проблемы совместимости.
-
Использование флагов: Некоторые инструменты поддерживают использование флагов для переключения между разными стандартами регулярных выражений. Например, использование
-E
или-P
в GNU grep. -
Инструменты преобразования: Существуют онлайн-платформы и инструменты, которые могут помочь в преобразовании регулярных выражений из одного формата в другой, автоматически учитывая синтаксические различия.
В заключение, важно понимать, что хотя может существовать значительное сходство между различными реализациями регулярных выражений, многие инструменты и языки программирования имеют свои собственные нюансы. Разобравшись в теоретических основах и используемых стандартах, вы сможете более эффективно адаптировать ваши регулярные выражения для использования в различных средах.