Первоначально опубликовано в моем блоге.
В моей команде каждые пару недель мы проводим «мастер-классы», на которых кто-то из команды представляет какую-то тему остальным членам команды.
Эта статья, по сути, является содержанием занятия по регулярным выражениям (иначе известным как regex), которое я провел недавно.
Это введение в основы регулярных выражений. Подобных статей много, но эта — моя.
- Что такое регулярное выражение (или regex)?
- Когда использовать regex
- Избегайте кодирования в regex, если это возможно.
- Когда следует использовать regex
- Другие полезные применения regex
- Как использовать regex
- Как выглядит regex
- Распространенные символы регулярных выражений
- Сокращения символов в regex
- Regex в условных выражениях
- Regex в подстановках
- Модификаторы регулярных выражений
- Lookahead и lookbehind в regex
- Ресурсы Regex
Что такое регулярное выражение (или regex)?
Википедия определяет регулярные выражения как:
«последовательность символов, определяющая шаблон поиска».
Они есть практически в каждом языке программирования, и, вероятно, чаще всего вы встретите их для поиска строк в условиях, которые слишком сложны для простых логических сравнений (например, «или», «и», «в»).
Несколько примеров регулярных выражений для начала:
Regex | Описание |
---|---|
[ -~] |
Любой символ ASCII (символы ASCII находятся между пробелом и «~») |
^[a-z0-9_-]{3,15}$ |
Имена пользователей от 3 до 15 символов |
Когда использовать regex
Используйте регулярные выражения с осторожностью. Сложность regex влечет за собой определенные издержки.
Избегайте кодирования в regex, если это возможно.
Некоторые люди, столкнувшись с проблемой, думают: «Я знаю, я буду использовать регулярные выражения». Теперь у них две проблемы». — Джейми Завински
В программировании используйте регулярные выражения только в крайнем случае. Не решайте важные проблемы с помощью regex.
- regex дорого стоит — regex часто является самой процессороемкой частью программы. А проверка несовпадающего regex может быть еще более дорогостоящей, чем проверка совпадающего.
- жадность regex — очень легко найти гораздо больше совпадений, чем предполагалось, что приводит к ошибкам. Мы неоднократно сталкивались с проблемами, когда регексы были слишком жадными и вызывали проблемы на наших сайтах. В большинстве регекс-движков можно сделать не жадное соответствие, но оно редко используется (спасибо @matthewpersico).
- regex непрозрачен — Даже людям, хорошо знающим regex, потребуется время, чтобы разобраться в новой строке regex, и они все равно будут делать ошибки. В долгосрочной перспективе это очень дорого обходится для поддержки проекта. (Посмотрите на этот удивительный regex для адресов электронной почты RFC822)
Всегда старайтесь знать обо всех возможностях языка для работы со строками и их проверки, которые могут помочь вам избежать регулярных выражений. В Python, например, ключевое слово in
, мощная индексация []
и такие строковые методы, как contains
и startswith
(которым можно передавать как строки, так и кортежи для нескольких значений), можно комбинировать очень эффективно.
Самое главное, регексы не должны использоваться для разбора строк. Вместо этого вы должны использовать или написать специальный парсер. Например, вы не можете разобрать HTML с помощью regex (в Python используйте BeautifulSoup; в JavaScript используйте DOM).
Когда следует использовать regex
Конечно, есть моменты, когда регулярные выражения можно или нужно использовать в программах:
- Когда они уже существуют, и вы должны их поддерживать (хотя, если вы можете их удалить, вы должны это сделать).
- проверка строк, когда нет другого варианта
- Манипуляция строками (подстановка), когда нет другого выбора.
Если вы пишете что-то большее, чем самый простой regex, любые сопровождающие вряд ли смогут легко понять ваш regex, поэтому вам стоит подумать о добавлении либеральных комментариев. Например, вот это в Python:
>>> pattern = """
^ # beginning of string
M{0,4} # thousands - 0 to 4 M's
(CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's),
# or 500-800 (D, followed by 0 to 3 C's)
(XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's),
# or 50-80 (L, followed by 0 to 3 X's)
(IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's),
# or 5-8 (V, followed by 0 to 3 I's)
$ # end of string
"""
>>> re.search(pattern, 'M', re.VERBOSE)
Другие полезные применения regex
Регулярные выражения могут быть чрезвычайно мощными для быстрого решения проблем, когда будущее обслуживание не вызывает опасений. Например:
- Grep (или Ripgrep), Sed, Less и другие инструменты командной строки.
- В редакторах (например, VSCode) для быстрого переформатирования текста.
Также стоит воспользоваться возможностью использовать regex этими способами, чтобы попрактиковать свои навыки работы с regex.
Например, недавно я использовал следующую подстановку regex в VSCode для форматирования дампа текста в формат таблицы:
Как использовать regex
Имейте в виду, что парсеры регулярных выражений бывают нескольких видов. В основном, каждый язык реализует свой собственный парсер. Тем не менее, парсер regex в Perl является золотым стандартом. Если у вас есть выбор, используйте Perl-совместимые регулярные выражения.
Как выглядит regex
Традиционный способ записи регулярного выражения — окружение его косой чертой.
/^he[l]{2}owworld$/
Именно так они записываются в Perl и JavaScript, а также во многих инструментах командной строки, таких как Less.
Однако многие современные языки (например, Python) решили не включать встроенный тип regex, и поэтому регулярные выражения записываются просто как строки:
r"^he[l]{2}owworld$"
Распространенные символы регулярных выражений
Символ | Описание |
---|---|
. |
Идентифицирует любой символ (кроме новой строки). |
|
Исключает специальный символ (например, . соответствует буквальной точке). |
? |
Предшествующий символ может присутствовать или отсутствовать (например, /hell?o/ будет соответствовать hello или helo ) |
* |
Допускается любое количество предшествующего символа (например, .* будет соответствовать любой однострочной строке, включая пустую строку, и часто используется). |
+ |
Один или более предшествующих символов (.+ — то же самое, что и .* , за исключением того, что он не будет соответствовать пустой строке) |
` | ` |
() |
сгруппировать раздел вместе. Это может быть полезно для условий (`(a |
{% raw %}{} |
Укажите, сколько символов предшествует предыдущему (например, a{12} соответствует 12 символам «a» подряд). |
[] |
Сопоставить любой символ из этого набора. - определяет диапазоны (например, [a-z] — любая строчная буква), ^ означает «не» (например, [^,]+ соответствует любому количеству незапятых в строке). |
^ |
Начало строки |
$ |
Конец строки |
Сокращения символов в regex
В большинстве реализаций regex вы можете использовать обратный слеш, за которым следует буква (x
), как сокращение для набора символов. Вот список некоторых из них, взятый из шпаргалки rexegg.com по regex.
Regex в условных выражениях
Самый простой случай использования регексов в программировании — это сравнение строк. В разных языках это выглядит по-разному, например:
// Perl
if ( "hello world" =~ /^he[l]{2}osworld$/ ) {..}
// JavaScript
if( /^he[l]{2}osworld$/.test("hello world") ) {..}
# Python
import re
if re.match(r"^he[l]{2}osworld$", "hello world"): ..
Regex в подстановках
Вы также можете использовать regex для манипулирования строками через подстановку. В следующих примерах будет выведено слово «mad world»:
// Perl
$hw = "hello world"; $hw =~ s/^(he[l]{2}o)s(world)$/mad 2/; print($hw)
// JavaScript
console.log("hello world".replace(/^(he[l]{2}o)s(world)$/, "mad $2"))
# Python
import re
print(re.replace(r"^(he[l]{2}o)s(world)$", r"mad 2", "hello world"))
Модификаторы регулярных выражений
Вы можете изменить поведение регулярных выражений на основе нескольких модификаторов. Здесь я проиллюстрирую только один из них, который является модификатором, делающим регулярные выражения нечувствительными к регистру. В Perl, JavaScript и других более традиционных контекстах regex модификаторы добавляются после последнего /
. В более современных языках вместо них часто используются константы:
// Perl
if ( "HeLlO wOrLd" =~ /^he[l]{2}osworld$/i ) {..}
// JavaScript
if( /^he[l]{2}osworld$/i.test("HeLlO wOrLd") ) {..}
# Python
import re
if re.match(r"^he[l]{2}osworld$", "HeLlO wOrLd", flags=re.IGNORECASE): ..
Lookahead и lookbehind в regex
Они поддерживаются только в некоторых реализациях регулярных выражений и дают вам возможность искать соответствие строк, которые предшествуют или следуют за другими строками, но без включения префикса или суффикса в само соответствие:
(Опять же, взято из шпаргалки regex на rexegg.com)
Ресурсы Regex
Это все, что у меня есть на данный момент. Если вы хотите узнать больше, есть много полезных ресурсов:
- Rexegg.com — множество отличных статей по большинству аспектов regex
- Regex101 — Тестер для вашего regex, предлагающий несколько различных реализаций.
- iHateRegex — Коллекция примеров шаблонов regex для поиска некоторых распространенных типов строк (например, номер телефона, адрес электронной почты).
- Официальная документация по Perl-совместимым регулярным выражениям