Почему мое регулярное выражение работает в X, но не работает в Y?

Вопрос или проблема

Я написал регулярное выражение, которое отлично работает в определенной программе (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).

Следующие функции также являются стандартными, но отсутствуют в некоторых ограниченных реализациях:

Следующие элементы являются обычными расширениями (особенно в инструментах 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 предлагает следующие операторы:

Shell глобы

Shell глобы (подстановочные знаки) выполняют сопоставление шаблонов, используя синтаксис, который полностью отличается от регулярных выражений и менее мощный. В дополнение к оболочкам эти подстановочные знаки доступны с другими инструментами, такими как find -name и фильтры rsync. POSIX-шаблоны включают следующие функции:

  • ? совпадает с любым одним символом.
  • […] – это набор символов, как в общих синтаксисах регулярных выражений. Некоторые оболочки не поддерживают классы символов. Некоторые оболочки требуют использования ! вместо ^ для отрицания набора.
  • * совпадает с любой последовательностью символов (часто кроме / при сопоставлении путей файлов; если / исключен из *, то ** иногда включает /, но проверьте документацию инструмента).
  • Обратная косая черта экранирует следующий символ.

Ksh предлагает дополнительные функции, которые делают его сопоставление шаблонов равносильным по мощности регулярным выражениям. Эти возможности также доступны в bash после выполнения shopt -s extglob. Zsh имеет другой синтаксис, но может также поддерживать синтаксис ksh после setopt ksh_glob.


¹ если только опция rematchpcre не включена в zsh, в этом случае =~ использует PCRE. Расширенные регулярные выражения ksh93 также поддерживают некоторые расширенные операторы Perl, такие как операторы просмотра.

Ответ или решение

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

Теоретическая основа

Регулярные выражения (regex) представляют собой мощный инструмент для поиска и работы с текстом на основе заданных шаблонов. Однако, из-за исторических причин, в различных инструментах и языках программирования существуют разные реализации и синтаксисы. Это может привести к проблемам совместимости между ними. Основные стандарты включают:

  1. POSIX Basic Regular Expressions (BRE): BRE – это базовый стандарт для регулярных выражений, обеспечивая базовые операции, такие как привязка к началу (^) и концу ($) строки, одиночный символ (.) и квантификатор (*).

  2. POSIX Extended Regular Expressions (ERE): ERE расширяет BRE, добавляя поддержку операторов, таких как +, ? и |, без необходимости экранирования.

  3. Perl-Compatible Regular Expressions (PCRE): Это расширение синтаксиса ERE, добавляющее такие функции, как ненулевые группы (?:…), положительные и отрицательные зацепы (?=…), (?!…), (?<=…), (?<!…), которые значительно расширяют возможности регулярных выражений.

Различные инструменты могут поддерживать эти стандарты по-разному. Например, инструмент grep может работать с разными вариантами регулярных выражений в зависимости от флага (-E для ERE или -P для PCRE). В то же время языки программирования, такие как Python или Ruby, используют свои библиотеки для реализации регулярных выражений, которые часто основываются на PCRE, но могут включать дополнительные особенности или ограничения.

Примеры различий в синтаксисе

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

  1. Квантификаторы и экранирование: В BRE, символы +, ?, | и {} требуют экранирования, тогда как в ERE они работают без экранирования. Например:

    • BRE: a\+ соответствует одной или более буквам "a".
    • ERE: a+ соответствует одной или более буквам "a".
  2. Альтернация: В BRE используется экранированный символ \|, тогда как в ERE – |. Пример:

    • BRE: foo\|bar соответствует либо "foo", либо "bar".
    • ERE: foo|bar соответствует либо "foo", либо "bar".
  3. Негативный и позитивный зацеп: PCRE позволяет использовать зацепы, которых нет в BRE или ERE. Например:

    • PCRE: (?<!foo)bar соответствует "bar", если перед ним не стоит "foo".

Применение и советы

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

  1. Изучение документации: Внимательно изучите документацию к инструменту или языку, который вы используете, чтобы понять, какой синтаксис регулярных выражений поддерживается. Часто можно найти таблицы сравнения, которые указывают на поддерживаемые функции и особенности.

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

  3. Использование флагов: Некоторые инструменты поддерживают использование флагов для переключения между разными стандартами регулярных выражений. Например, использование -E или -P в GNU grep.

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

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

Оцените материал
Добавить комментарий

Капча загружается...