[Перевод] Регулярные выражения — это не трудно

image


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

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

ПРИМЕЧАНИЕ:
В некоторых языках, например в Rust, есть синтаксические комбинаторы, которые не хуже или даже лучше, чем регулярные выражения, срабатывают в большинстве важных для меня аспектов. Однако я все равно часто выбираю регулярные выражения, так как они лучше укладываются в голове. Существует одно базовое подмножество регулярных выражений, поддерживаемое во всех основных языках программирования.

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

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

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


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

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

Диапазоны
Установим соответствия между одним из множества символов.
• [a] — то же, что и просто a
• [abc] — Совпадает с a, b, или c.
• [a-c] — То же самое, но с применением знака — для указания диапазона символов
• [a-z] — любой символ нижнего регистра
• [a-zA-Z] — любой строчный или прописной символ
• [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 разрабатывал идею регулярных выражений. Однако более новые реализации достаточно стабильны.

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

© Habrahabr.ru