image

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

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

ПРИМЕЧАНИЕ:

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

Существует четыре основных понятия, которые необходимо знать
  1. Наборы символов
  2. Повторение
  3. Группы
  4. Операторы |, ^ и $

Здесь я расскажу о подмножестве регулярных выражений, которое несложно понять и запомнить. Кроме того, я расскажу, что следует игнорировать. Большинство из этих вещей – это сокращения (шорткаты), которые позволяют немного сократить писанину за счет некоторого усложнения. Я предпочитаю многословность вместо сложности, поэтому я придерживаюсь этого подмножества.

Наборы символов


Набор символов — это наименьшая единица сопоставления текста, доступная в регулярных выражениях. Это всего лишь один символ.

Одиночные символы


a соответствует одному символу, всегда строчному a. aaa — это 3 последовательных набора символов, каждый из которых соответствует только a. То же самое с abc, но второй и третий соответствуют b и c соответственно.

Диапазоны


Установим соответствия между одним из множества символов.
  1. [a] — то же, что и просто a
  2. [abc] — Совпадает с a, b, или c.
  3. [a-c] — То же самое, но с применением знака — для указания диапазона символов
  4. [a-z] — любой символ нижнего регистра
  5. [a-zA-Z] — любой строчный или прописной символ
  6. [a-zA-Z0-9!@#$%^&*()-] — алфавитно-цифровой и любой из этих символов: !@#$%^&*()-

Обратите внимание на то, что в последнем пункте символ — стоит последним. Также обратите внимание, что ^ не является первым символом в диапазоне, ^ может стать оператором, если он встречается в качестве первого символа в наборе символов или регулярном выражении.

Здесь есть параллель с логикой булевых операций:
  • ab означает “a И b”
  • [ab] означает “a ИЛИ b”

Можно построить более сложную логику, используя группы и отрицание.

Отрицание (^)


Я упомяну этот оператор позже, но в контексте наборов символов он означает «все, кроме этих».

Пример:
  • [^ab] означает «все, кроме a или b».
  • [ab^] означает «a, b или ^». Для того чтобы символ ^ имел особое значение, он должен быть первым.

[Не обращайте внимания]


Эти вещи чрезмерно сложны, зато и код помогают сократить очень сильно.
  • \w, \s, и т.д. — Это сокращения для диапазонов типа [a-zA-Z0-9]. Не используйте их, поскольку они не портируются. В большинстве языков программирования они так или иначе присутствуют, но их трудно запомнить. В некоторых языках используется другой синтаксис, например :word:, который почти такой же длинный, как и развёрнутое написание.
  • . — Точка (.) совпадает с любым символом, но не всегда. Иногда она не совпадает с новыми строками. В некоторых языках программирования она никогда не совпадает с новыми строками. Я слишком часто попадался на том, что точка. ведет себя не так, как мне кажется. Лучше всего этот символ полностью игнорировать. Вместо этого используйте отрицание диапазона, например [^%], если вы знаете, что символ % не появится. Не помешает в таких случаях выражать идеи как можно более явно.

Повторение


Эти операторы изменяют (непосредственно) предыдущий набор символов на другой, совпадающий определенное количество раз:
  • ? — ноль или один
  • * — ноль или более
  • + — один или более

Все это также применимо и к целым группам.

[Не обращайте внимания]


Они неоправданно сложны. То же самое можно сделать и другими средствами.
  • Нежадное сопоставление, *? и +? Это часто встречается при использовании набора символов. Вместо этого обычно можно использовать более строгий набор символов отрицания, например [^%].
  • Диапазоны повторения, например, {1,2}. Просто продублируйте свой шаблон или используйте? или * в группе.

Группы


Группа — это, по сути, подвыражение. Группу можно использовать тремя основными способами:

1. Повторение подшаблона


Например, шаблон ([0-9][0-9]?[0-9]][.])+ соответствует одной, двум или трем цифрам, за которыми следует символ. и также соответствует повторяющимся шаблонам этого типа. Например, может соответствовать IP-адресу (хотя и не совсем точно).

2. Подстановки


Наиболее распространенными операциями, выполняемыми при помощи регулярных выражений, являются операции сопоставления и подстановки. Однако API для подстановки сильно различаются в зависимости от конкретного языка.
  • Методы — в C#, Java, Python и т.д. обычно имеется метод или функция с названием типа sub, substitute или replace.
  • Стиль sed — в sed, Perl и bash имеет вид s/pattern/replacement/, где ведущая s означает «заменить».

В обоих случаях можно использовать $1 или \1. Посмотрите в документации, какой вариант подходит лучше.

3. Извлечение текста


Вы можете извлечь текст, которому соответствует группа.
  • 0 — полное соответствие выражению
  • 1-∞ — текст, которому соответствует группа с индексом 1. Первый набор круглых скобок — это группа 1, второй — 2 и т. д.

Непортируемость заключается в том, что API для доступа к группам почти во всех языках программирования разные. Тем не менее, извлечение групп чрезвычайно полезно, так что просто посмотрите, что это такое.

Наиболее распространенные API выглядят следующим образом:
  • Match.group(1) — В Python, C#, Java и т.д. существует метод из основного языка программирования для извлечения группы из объекта match. Точное название метода обычно звучит как group или getGroup.
  • $1 — Perl будет устанавливать переменные типа $1 и $2 в локальной области видимости. Большинство языков программирования этого делать не умеют, но вы увидите, как появляется такой синтаксис, например, при замене часто в тексте подстановки можно использовать либо $1, либо \1.

Если таких API не существует или их лень запоминать, можно воспроизвести извлечение с помощью подстановки. Например, в Python для извлечения первой группы можно выполнить re.sub("([^\n]*\\\.foo)[^\n]*", "$1", input_str)

[Не обращайте внимания]


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

Операторы The, |, ^ и $


Оператор | — это оператор OR, но для целых регулярных выражений или групп.
  • foo|bar соответствует либо foo, либо bar
  • (foo|bar)+ добавляет некоторые повторы, например, соответствует barfoobarfoo

Знак ^ имеет значение только тогда, когда он является первым символом:
  • Первый символ в шаблоне — совпадение с началом строки или строки. Например, ^foo будет соответствовать foobar но не barfoo.

    ВНИМАНИЕ:
    Некоторые regex API всегда ведут себя так, будто шаблон всегда заключен в ^ и $. Это можно легко проверить методом проб и ошибок.
  • Первый символ в наборе — отрицание, совпадает все, кроме этих символов

Символ $ означает только «конец» и используется только в regex верхнего уровня.

Заключение


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

Что касается портирования — в большинстве современных реализаций пытаются скопировать некоторое подмножество регулярных выражений Perl. Подмножество, которое я описал здесь, достаточно единообразно для всех основных современных языков программирования. Тем не менее, вы можете столкнуться с некоторыми сюрпризами, если используете старые инструменты, такие как sed и grep, которые были созданы примерно в то же время, когда Perl разрабатывал идею регулярных выражений. Однако более новые реализации достаточно стабильны.

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

Комментарии (17)


  1. dlinyj
    03.11.2023 12:02
    +8

    Регулярки - не сложно, но стоит ими не пользоваться какое-то время и полностью выветриваются из головы.


    1. Krouler7
      03.11.2023 12:02
      +2

      Благо есть куча сервисов, которые позволяют быстро вспомнить.


    1. Leetc0deM0nkey
      03.11.2023 12:02
      +5

      Да чем угодно не пользуйся какое-то время, и оно выветривается. И особенно печально когда от тебя на собесе хотят "вынь да положь".


      1. kirillbelash93
        03.11.2023 12:02

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


  1. igorts
    03.11.2023 12:02
    +2

    очередной урезанный справочник :(

    но регулярки обожаю!


  1. holodoz
    03.11.2023 12:02
    +1

    Испанский язык очень простой, запомните "ола! " и "дос сервезас, пор фавор". Есть и другие слова, но он необоснованно усложняют язык и легко заменяются на уже изученные конструкции


  1. saboteur_kiev
    03.11.2023 12:02
    +1

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

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


    1. ReinRaus
      03.11.2023 12:02
      +2

      Добавьте подзаголовок "Рубрика вредные советы" для:

      • Нежадное сопоставление, *? и +? Это часто встречается при использовании набора символов. Вместо этого обычно можно использовать более строгий набор символов отрицания, например [^%].

      • Диапазоны повторения, например, {1,2}. Просто продублируйте свой шаблон или используйте? или * в группе.

      На полном серьёзе такие замены называются "упрощением"


      1. ReinRaus
        03.11.2023 12:02

        Не туда написал. Хотел под основной трэд написать, но промахнулся. С телефона не вижу кнопки "удалить комментарий".


        1. saboteur_kiev
          03.11.2023 12:02

          Да в общем-то туда, как раз получаем
          "повторения", "нежадное сопоставление",
          А ведь и то и то - квантификаторы.


  1. Batalmv
    03.11.2023 12:02
    +1

    regex101: build, test, and debug regex

    • можно под конкретный язык

    • есть короткая и куда более полезная аннотация

    • ответ отлично парсится и описывается

    • ....

    Понятно есть много других, но я, если надо отладить выражение и процесс начинает стопориться, иду на него

    Статья - просто трата времени


    1. igorts
      03.11.2023 12:02

      Я уже боюсь комментарии с критикой писать, сразу минус в карму прилетает

      буду только благожелательные


      1. igorts
        03.11.2023 12:02

        Имею ввиду критику на статью


  1. apevzner
    03.11.2023 12:02

    Вообще так, невредно понимать разницу в возможностях между регулярным конечным автоматом, всё состояние которого, грубо говоря, укладывается в одну переменную типа int, и стековым конечным автоматом, состояние которого - стек переменных типа int (тоже грубо говоря).

    Например, никакая регулярка не сможет проверить сбалансированность скобок, а стековый автомат сделает это с лёгкостью.


    1. Alexandroppolus
      03.11.2023 12:02

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


  1. VADemon
    03.11.2023 12:02
    +1

    Обстоятельная статья на эту тему была: https://habr.com/ru/articles/545150/

    Зачем плодить сущности - непонятно, тем более когда статья и не туда и не сюда. Чем мне нравится та статья, там достаточно примеров до и после, а именно этим понимается различие и работа регулярок (если кто-то не может продумать путь от конечных автоматов к регуляркам). Здесь - сухое описание не лучше дескриптивной документации. Мне также не нравится авторский стиль "ignore this" (или его форма прятать под спойлеры). Это текст и читается он сверху вниз и весь. Если бы это было введением к чему-то - ладно, но само по себе оно не состоятельно.

    Здесь есть параллель с логикой булевых операций:

    ab означает “a И b”

    Не согласен с таким объяснением для новичков, это "a И ЗАТЕМ СЛЕДУЕТ b".


  1. martin_wanderer
    03.11.2023 12:02

    Категорически не согласен с "не используйте \w". Именно на шорткатах получаются действительно простые регулярки, которые даже легко читать. И поддерживаются они почти везде. В остальном же весьма любопытное изложение