User Artur
Artur
40 уровень
Tallinn

RegEx: 20 коротких шагов для освоения регулярных выражений. Часть 1

Статья из группы Random
Оригинал этой статьи здесь. Наверное, теории много не бывает, и я приведу несколько ссылок на более подробный материал по regex в конце статьи. Но, мне показалось, что начинать вникать в такую тему как регулярные выражения будет гораздо интереснее, если есть возможность заниматься не только зубрежкой, но и сразу закреплять знания, выполняя небольшие задания по ходу обучения. RegEx: 20 коротких шагов для освоения регулярных выражений. Часть 1 - 1Пожалуй приступим. Обычно противники использования регулярных выражений ('RegEx' или просто 'regex') в программировании приводят следующую цитату, приписываемую Джейми Завински: "Некоторые люди, сталкиваясь с проблемой, думают: "Я знаю, я буду использовать регулярные выражения". Теперь у них две проблемы". На самом деле, использование регулярных выражений еще не является хорошей или плохой идеей. И это само по себе не добавит проблем и не решит ни одну из них. Это всего-лишь инструмент. И то, как вы его используете (правильно или неправильно), определяет, какие результаты вы увидите. Если вы попытаетесь использовать regex, например, для создания HTML-парсера, то вы, скорее всего, испытаете боль. Но если вы хотите просто извлечь, например, временные метки из некоторых строк, у вас, вероятно, будет все в порядке. Чтобы облегчить вам освоение регулярных выражений, я собрал этот урок, который поможет вам с нуля овладеть регулярными выражениями всего за двадцать коротких шагов. Это руководство в основном фокусируется на основных понятиях регулярных выражений и углубляется в более сложные темы только по мере необходимости.

Шаг 1: для чего нужно использовать регулярные выражения

RegEx: 20 коротких шагов для освоения регулярных выражений. Часть 1 - 2Регулярные выражения используются для поиска совпадений в тексте по заданным шаблонам (образцам). При помощи regex мы можем легко и просто извлекать изюм из кекса слова из текста, а также отдельные литеральные (буквальные) и мета (специальные) символы и их последовательности, отвечающие определенным критериям. Вот что говорит нам о них Википедия: Регуля́рные выраже́ния (англ. regular expressions) — формальный язык поиска и осуществления манипуляций с подстроками в тексте, основанный на использовании метасимволов (символов-джокеров, англ. wildcard characters). Для поиска используется строка-образец (англ. pattern, по-русски её часто называют "шаблоном", "маской"), состоящая из символов и метасимволов и задающая правило поиска. Для манипуляций с текстом дополнительно задаётся строка замены, которая также может содержать в себе специальные символы. Шаблон может быть таким-же простым, как, например, слово dog в этом предложении:
The quick brown fox jumps over the lazy dog.
Это регулярное выражение выглядит так:
dog
... Достаточно легко, неправда-ли? Образцом может быть также любое слово, которое содержит букву o. Регулярное выражение для поиска такого шаблона может выглядеть так:
\w*o\w*
(Опробовать это регулярное выражение можно здесь), Можно заметить, что по мере усложнения требований к "соответствию", регулярное выражение также усложняется. Существуют дополнительные формы записи для указания групп символов и соответствия повторяющимся шаблонам, что я объясню ниже. Но, как только мы находим соответствие шаблону в каком-то тексте, то что-же мы можем с ним делать? Современные движки регулярных выражений позволяют извлекать символы или их последовательности (подстроки) из содержащегося текста, или удалять их, или заменять их другим текстом. В общем, регулярные выражения используются для разбора и манипулирования текстом. Мы можем извлечь, например, подстроки, которые выглядят как IP-адреса, а затем попытаться проверить их. Или мы можем извлечь имена и адреса электронной почты и сохранить их в базе данных. Или использовать регулярные выражения, чтобы найти конфиденциальную информацию (например, номера паспортов или номера телефонов) в электронных письмах и предупредить пользователя о том, что он может подвергнуть себя риску. Regex действительно универсальный инструмент, который легко выучить, но трудно освоить: "Точно так же, как есть разница между хорошим исполнением музыкального произведения и созданием музыки, есть и разница между знанием регулярных выражений и их пониманием". - Джеффри Э. Ф. Фридл, Освоение регулярных выражений

Шаг 2: квадратные скобки []

Простейшие регулярные выражения, которые легко понять - это те, которые всего-лишь ищут соответствие по-символьно между шаблоном регулярного выражения и целевой строкой. Давайте, например, попробуем найти кота: RegEx: 20 коротких шагов для освоения регулярных выражений. Часть 1 - 3
pattern: cat
string:  The cat was cut when it ran under the car.
matches:     ^^^
(Как это работает в деле - смотрите здесь) NB! Все решения представлены здесь только как варианты решений. В регулярных выражениях, как и в программировании вообще, можно решать одни и те же задачи разными способами. Однако, кроме строгого по-символьного сравнения, мы также можем указать альтернативные совпадения, используя квадратные скобки:
pattern: ca[rt]
string:  The cat was cut when it ran under the car.
matches:     ^^^             		           ^^^
(Как это работает) Открывающие и закрывающие квадратные скобки сообщают механизму регулярных выражений, что он должен искать любой из указанных символов, но только один. Вышеуказанное регулярное выражение не найдет, например, слово cart целиком, а найдет только его часть:
pattern: ca[rt]
string:  The cat was cut when it ran under the cart.
matches:     ^^^                               ^^^
(Как это работает) Когда вы используете квадратные скобки, вы указываете механизму регулярных выражений, чтобы он искал совпадения только с одним из символов, содержащихся в скобках. Движок находит символ c, потом символ a, но если следующий символ не r или t, то это еще не полное совпадение. Если он находит ca, а затем либо r, либо t, он останавливается. Он не будет пытаться сопоставить больше символов, потому что квадратные скобки указывают, что нужно искать только один из содержащихся символов. Когда он находит ca, то следующим находит r в слове cart, и останавливается, потому что он уже нашел совпадение последовательности car.

Задачи для тренировки:

Напишите регулярное выражение, которое находит все 10 совпадений с шаблонами had и Had в этом отрывке непереводимой игры слов на местном диалекте:
pattern:
string:  Jim, where Bill had had "had", had had "had had". "Had had" had been correct.
matches:                 ^^^ ^^^  ^^^   ^^^ ^^^  ^^^ ^^^    ^^^ ^^^  ^^^
(Смотрите возможное решение здесь) А как насчет всех названий животных в следующем предложении?
pattern:
string:  A bat, a cat, and a rat walked into a bar...
matches:   ^^^    ^^^        ^^^
(Возможное решение) Или еще проще: найдите слова bar или bat:
pattern:
string:  A bat, a cat, and a rat walked into a bar...
matches:   ^^^                                 ^^^
(Возможное решение) Вот мы уже и научились писать более-менее сложные регулярные выражения, и мы только на шаге 2! Продолжаем!

Шаг 3: escape-последовательности

RegEx: 20 коротких шагов для освоения регулярных выражений. Часть 1 - 4На предыдущем шаге мы узнали о квадратных скобках [] и о том, как они помогают нам найти альтернативные совпадения при помощи движка regex. А что если мы хотим найти совпадения в виде самих открытых и закрытых квадратных скобок []? Когда мы хотели найти по-символьное совпадение со словом cat, то мы предоставляли движку regex эту последовательность символов (cat). Давайте попробуем найти квадратные скобки [] таким же способом:
pattern: []
string:  You can't match [] using regex! You will regret this!
matches: 
(Смотрим что получилось) Что-то не сработало, однако... Это происходит потому, что символы квадратной скобки работают как специальные символы движка regex, которые обычно используются для обозначения чего-то иного, и не являются буквальным шаблоном для поиска их самих. Как мы помним из шага 2, они используются для поиска альтернативных совпадений, чтобы движок regex мог найти соответствия любому из символов, содержащихся между ними. Если вы не поместите никаких символов между ними, это может привести к ошибке. Чтобы найти соответствия этим особым символам, мы должны экранировать их, поставив перед ними символ backslash \. Backslash (или обратный слэш) это еще один специальный символ, который сообщает движку regex что надо искать следующий символ буквально, а не использовать его как метасимвол. Движок regex будет искать символы [ и ] буквально, только если им обоим будет предшествовать обратный слэш:
pattern: \[\]
string:  You can't match [] using regex! You will regret this!
matches:                 ^^ 
(Смотрим что получилось на этот раз) ОК, а если мы хотим найти сам обратный слэш? Ответ прост. Поскольку backslash \ тоже является специальным символом, то его тоже нужно экранировать. Чем? Обратным слэшем же!
pattern: \\
string:  C:\Users\Tanja\Pictures\Dogs
matches:   ^     ^     ^        ^
(Этот же пример на деле) Только специальным символам должен предшествовать backslash. Все остальные символы интерпретируются буквально по умолчанию. Например, регулярное выражение t буквально соответствует только букве t в нижнем регистре:
pattern: t
string:  t  t   t   t
matches: ^  ^   ^   ^
(Пример) Однако, такая последовательность как \t работает иначе. Она представляет из себя шаблон для поиска символа табуляции:
pattern: \t
string:  t  t   t   t
matches:  ^  ^   ^
(Пример) Некоторые распространенные escape-последовательности включают в себя \n (разрывы строк в стиле UNIX) и \r (используются в разрывах строк в стиле Windows, \r\n). \r является символом "возврата каретки", а \n является символом "перевода строки", оба из которых были определены вместе со стандартом ASCII, когда телетайпы еще находились в повсеместном использовании. Другие распространенные escape-последовательности будут рассмотрены в этом руководстве позже.

А пока закрепим материал парой несложных задачек:

Попробуйте написать регулярное выражение для поиска... регулярного выражения ;) Результат должен быть примерно таким:
pattern:
string:  ...match this regex `\[\]` with a regex?
matches:                      ^^^^	
(Решение) Справились? Молодцы! А теперь попробуйте создать regex для поиска таких escape-последовательностей:
pattern:
string:  `\r`, `\t`, and `\n` are all regex escape sequences.
matches:  ^^    ^^        ^^
(Решение)

Шаг 4: ищем "any" (любой) символ при помощи точки .

RegEx: 20 коротких шагов для освоения регулярных выражений. Часть 1 - 5При написании решений для поиска escape-последовательностей, которые мы видели на предыдущем шаге, вы, возможно, задавались вопросом: "Могу ли я сопоставить символ обратной косой черты, а затем любой другой символ, следующий за ним?"... Конечно можете! Есть еще один специальный символ, который используется для поиска соответствия (почти) любому символу - это символ точки (полной остановки). Вот что он делает:
pattern: .
string:  I'm sorry, Dave. I'm afraid I can't do that.
matches: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^	
(Пример) Если-же вы хотите сопоставить только шаблоны, которые выглядят как escape-последовательности, вы можете сделать что-то вроде этого:
pattern: \\.
string:  Hi Walmart is my grandson there his name is "\n \r \t".
matches:                                              ^^ ^^ ^^	
(Пример) И, как и со всеми специальными символами, если вы хотите сопоставить литерал., то вам нужно поставить перед ним символ \:
pattern: \.
string:  War is Peace. Freedom is Slavery. Ignorance is Strength.
matches:             ^                   ^                      ^
(Пример)

Шаг 5: диапазоны символов

RegEx: 20 коротких шагов для освоения регулярных выражений. Часть 1 - 6Что если вам нужны не любые символы, а вы хотите найти в тексте только буквы? Или цифры? Или гласные? Поиск по классам символов и их диапазонам позволит нам достичь этого.
`\n`, `\r`, and `\t` are whitespace characters, `\.`, `\\` and `\[` are not.	
Символы являются "пробелами", если они не создают видимой отметки в тексте. Пробел " " - это пробел, разрыв строки или табуляция. Предположим, мы хотим найти escape-последовательности, представляющие только пробельные символы \n, \r и \t в приведенном выше отрывке, но не другие escape-последовательности. Как мы могли-бы это сделать?
pattern: \\[nrt]
string:  `\n`, `\r`, and `\t` are whitespace characters, `\.`, `\\` and `\[` are not.
matches:  ^^    ^^        ^^	
(Пример) Это работает, но это не очень элегантное решение. Что, если позже нам нужно будет сопоставить escape-последовательность для символа "подача формы", \f? (Этот символ используется для обозначения разрывов страниц в тексте.)
pattern: \\[nrt]
string:  `\n`, `\r`, `\t`, and `\f` are whitespace characters, `\.`, `\\` and `\[` are not.
matches:  ^^    ^^    ^^	
(Нерабочее решение) При таком подходе нам нужно отдельно перечислять каждую строчную букву, которую мы хотим сопоставить, в квадратных скобках. Более простой способ сделать это - использовать диапазоны символов для соответствия любой строчной букве:
pattern: \\[a-z]
string:  `\n`, `\r`, `\t`, and `\f` are whitespace characters, `\.`, `\\` and `\[` are not.
matches:  ^^    ^^    ^^        ^^	
(А так уже работает) Диапазоны символов работают так, как вы могли бы ожидать, учитывая приведенный выше пример. Поместите в квадратные скобки первую и последнюю буквы, которые вы хотите сопоставить, с дефисом между ними. Например, если вы хотите найти только "комплекты" из backslash \ и одной буквы от a до m, вы можете сделать следующее:
pattern: \\[a-m]
string:  `\n`, `\r`, `\t`, and `\f` are whitespace characters, `\.`, `\\` and `\[` are not.
matches:                        ^^	
(Пример) Если вы хотите сопоставить несколько диапазонов, просто разместите их вплотную между квадратных скобок:
pattern: \\[a-gq-z]
string:  `\n`, `\r`, `\t`, and `\f` are whitespace characters, `\.`, `\\` and `\[` are not.
matches:        ^^    ^^        ^^	
(Пример) Другие общие диапазоны символов включают в себя: A-Z и 0-9

Испробуем их на практике, и решим пару задачек:

Шестнадцатеричные числа могут содержать цифры 0-9, а также буквы A-F. При использовании их для указания цветов, шестнадцатеричные коды могут содержать не более трех символов. Создайте регулярное выражение, чтобы найти действительные шестнадцатеричные коды в списке ниже:
pattern:
string:  1H8 4E2 8FF 0P1 T8B 776 42B G12
matches:     ^^^ ^^^         ^^^ ^^^	
(Решение) Используя диапазоны символов, создайте регулярное выражение, которое будет выбирать только строчные согласные (не гласные буквы, включая y) в предложении ниже:
pattern:
string:  The walls in the mall are totally, totally tall.
matches:  ^  ^ ^^^  ^ ^^  ^ ^^  ^  ^ ^ ^^^  ^ ^ ^^^ ^ ^^	
(Решение)

Шаг 6: "not", caret, циркумфлекс, знак вставки... символ ^

RegEx: 20 коротких шагов для освоения регулярных выражений. Часть 1 - 7Поистине, over 9000 имен у этого символа :) Но, для простоты, пожалуй остановимся на "not". Мое решение последней задачи немного длинное. Потребовалось 17 символов, чтобы сказать: "получить весь алфавит, кроме гласных". Конечно, есть более простой способ сделать это. Знак "not" ^ позволяет нам определять символы и диапазоны символов, которые должны не соответствовать указанным в шаблоне. Более простое решение последней задачи, приведенной выше, состоит в том, чтобы найти символы, которые не обозначают гласные буквы:
pattern: [^aeiou]
string:  The walls in the mall are totally, totally tall.
matches: ^^ ^^ ^^^^ ^^^^ ^^ ^^^ ^ ^^ ^ ^^^^^^ ^ ^^^^^ ^^^	
(Пример) Знак "not" ^ как крайний левый символ в квадратных скобках [] указывает механизму регулярных выражений на совпадение с одним (любым) символом, которого нет в квадратных скобках. Это означает, что приведенное выше регулярное выражение также соответствует всем пробелам, точке ., запятой , и заглавной T в начале предложения. Чтобы исключить их, мы можем точно также поместить их в квадратные скобки:
pattern: [^aeiou .,T] 
string:  The walls in the mall are totally, totally tall.
matches:  ^  ^ ^^^  ^ ^^  ^ ^^  ^  ^ ^ ^^^  ^ ^ ^^^ ^ ^^	
(Пример) Обратите внимание, что в этом случае, нам не нужно экранировать точку обратным слэшем, как мы делали прежде, когда искали ее, не пользуясь при этом квадратными скобками. Многие специальные символы в квадратных скобках обрабатываются буквально, включая открытый [- но не закрывающий ] символ скобки (догадываетесь почему?). Символ обратной косой черты \ тоже не трактуется буквально. Если вы хотите сопоставить литеральную (буквальную) обратную косую черту \ с использованием квадратных скобок, то вы должны экранировать ее, поставив перед ней следующую обратную косую черту \\. Такое поведение было назначено для того, чтобы символы пробелов тоже можно было разместить в квадратных скобках для сопоставления:
pattern: [\t]
string:  t  t   t   t
matches:  ^  ^   ^
(Пример) Знак "not" ^ также может быть использован с диапазонами. Если бы я хотел захватить только символы a, b, c, x, y и z, я мог бы сделать например так:
pattern: [abcxyz]
string:  abcdefghijklmnopqrstuvwxyz
matches: ^^^                    ^^^
(Пример) ... или, я мог бы указать, что я хочу найти любой символ, который находится не между d и w:
pattern: [^d-w]
string:  abcdefghijklmnopqrstuvwxyz
matches: ^^^                    ^^^
(Пример) Однако, будьте осторожны с "not" ^. Легко подумать "ну, я указал [^ b-f], поэтому я должен получить строчную букву a или что-то после f. Это не тот случай. Это регулярное выражение будет соответствовать любому символу, не входящему в этот диапазон, включая буквы, цифры, знаки препинания и пробелы.
pattern: [^d-w]
string:  abcdefg h.i,j-klmnopqrstuvwxyz
matches: ^^^    ^ ^ ^ ^             ^^^
(Пример)

Задачки для прокачки:

Используйте знак "not" ^ в квадратных скобках, чтобы сопоставить все слова ниже, которые не заканчиваются на y:
pattern:
string:  day dog hog hay bog bay ray rub
matches:     ^^^ ^^^     ^^^         ^^^	
(Решение) Напишите регулярное выражение, используя диапазон и знак "not" ^, чтобы найти все годы между 1977 и 1982 (включительно):
pattern:
string:  1975 1976 1977 1978 1979 1980 1981 1982 1983 1984
matches:           ^^^^ ^^^^ ^^^^ ^^^^ ^^^^ ^^^^
(Решение) Напишите регулярное выражение для поиска всех символов, которые не являются символом знака "not" ^ :
pattern:
string:  abc1^23*()
matches: ^^^^ ^^^^^	
(Решение)

Шаг 7: классы символов

Классы символов даже проще, чем диапазоны символов. Различные движки регулярных выражений имеют разные доступные классы, поэтому здесь я расскажу только об основных. (Проверьте, какую версию regex вы используете, потому что их может быть больше - или они могут отличаться от показанных здесь.) Классы символов работают почти как диапазоны, но при этом, вы не можете указать значения 'start' и 'end':
класс символы
\d "цифры" [0-9]
\w "символы слова" [A-Za-z0-9_]
\s "пробелы" [ \t\r\n\f]
Класс символов \w "word" особенно полезен, поскольку этот набор символов часто требуется для допустимых идентификаторов (имен переменных, функций и т.д.) в различных языках программирования. Мы можем использовать \w, чтобы упростить регулярное выражение, которое мы видели ранее:
pattern: \\[a-z]
string:  `\n`, `\r`, `\t`, and `\f` are whitespace characters, `\.`, `\\` and `\[` are not.
matches:  ^^    ^^    ^^        ^^	
Используя \w мы можем написать так:
pattern: \\\w
string:  `\n`, `\r`, `\t`, and `\f` are whitespace characters, `\.`, `\\` and `\[` are not.
matches:  ^^    ^^    ^^        ^^	
(Пример)

2 задачи для удачи:

Как мы с вами знаем, в Java идентификатор (имя переменной, класса, функции и т.д.) может начинаться только с буквы a-zA-Z, знака доллара $ или подчеркивания _. (подчеркивание, конечно плохой стиль, но компилятор пропускает, прим. переводчика). Остальная часть символов должна быть символами "word" \w. Используя один или несколько классов символов, создайте регулярное выражение для поиска допустимых идентификаторов Java среди следующих трехсимвольных последовательностей:
pattern:
string:  __e $12 .x2 foo Bar 3mm
matches: ^^^ ^^^     ^^^ ^^^	
(Решение) Номера социального страхования США (SSN) представляют собой 9-значные номера в формате XXX-XX-XXXX, где каждый X может быть любой цифрой [0-9]. Используя один или несколько классов символов, напишите регулярное выражение, чтобы найти правильно отформатированные номера SSN в списке ниже:
pattern:
string:  113-25=1902 182-82-0192 H23-_3-9982 1I1-O0-E38B
matches:             ^^^^^^^^^^^
(Решение) RegEx: 20 коротких шагов для освоения регулярных выражений. Часть 2. 20 коротких шагов для освоения регулярных выражений. Часть 3. RegEx: 20 коротких шагов для освоения регулярных выражений. Часть 4.
Комментарии (17)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
hidden #2399575 Уровень 8
14 февраля 2021
Спасибо за труд, оч хорошая статья да еще и с задачками
Oleksandr Bahno Уровень 22, Днепр, Украина
21 ноября 2020
DEL
Андрей Уровень 20, Винница, Украина
24 июня 2020
Оскара автору ! ! ! Так сказать целый-самостоятельный-отдельный раздел JavaRush с блэкджеком и Бендером :)
Vlad Уровень 28
18 июня 2020
Первая часть освоена, показалась легче, чем ожидал) спасибо автору)
Andrii Lobinskyi Уровень 16, Kramatorsk, Украина
21 мая 2020
Интересная тема. Полезная статья. Спасибо автору. И, конечно, переводчику )
Aleksei Уровень 35 Expert
19 мая 2020
Класс! Большое спасибо!
Алексей Уровень 27, Минск, Беларусь
6 мая 2020
Лучшие лекции по регуляркам что я видел! спасибо, мужик!
Alexandr Уровень 18, Харьков, Украина
3 мая 2020
Вы не рассказали ещё о многих символах и многом ещё. Ой. извините, не увидел, что это только первая статься из многих.
Alexandr Уровень 18, Харьков, Украина
3 мая 2020
В задаче:

Используйте знак "not" ^ в квадратных скобках, чтобы сопоставить все слова ниже, которые не заканчиваются на y:
есть упущение, что обязательно должен быть пробел после y т.е. последнее слово будет неправильно обрабатываться.
Владимир Созанский Уровень 24, Одесса, Украина
1 мая 2020
Спасибо. Очень хорошая статья.