Предыдущая лекция | Следующая лекция | |
---|---|---|
Типы файлов: CSV, XML, JSON. | Содержание | ООП. Базовые понятия. |
Регулярное выражение — это строка, задающая шаблон поиска подстрок в тексте. Одному шаблону может соответствовать много разных строчек.
Термин «Регулярные выражения» является переводом английского словосочетания «Regular expressions».
Регулярное выражение, или коротко «регулярка», состоит из обычных символов и специальных командных последовательностей.
Например, \d
задаёт любую цифру, а \d+
— задает любую последовательность из одной или более цифр.
Работа с регулярками реализована во всех современных языках программирования.
Однако существует несколько «диалектов», поэтому функционал регулярных выражений может различаться от языка к языку (реализация регулярных выражений в C# совместима с PERL).
Регулярка | Её смысл |
---|---|
simple text | В точности текст «simple text» |
\d{5} |
Последовательности из 5 цифр\d - означает любую цифру{5} - ровно 5 раз |
\d\d/\d\d/\d{4} |
Даты в формате ДД/ММ/ГГГГ (и прочие строки, на них похожие, например: 98/76/5432) |
\b\w{3}\b |
Слова в точности из трёх букв\b означает границу слова (с одной стороны буква, а с другой — нет)\w - любая буква,{3} - ровно три раза |
[-+]?\d+ |
Целое число, например, 7, +17, -42, 0013 (возможны ведущие нули)[-+]? - либо "-", либо "+", либо пусто\d+ - последовательность из 1 или более цифр |
[-+]?(?:\d+(?:\.\d*)? |\.\d+)(?:[eE][-+]?\d+)? |
Действительное число, возможно в экспоненциальной записи Например, 0.2, +5.45, -.4, 6e23, -3.17E-14. |
Регулярные выражения — это очень мощный инструмент.
Но использовать их следует с умом и осторожностью, и только там, где они действительно приносят пользу, а не вред.
Во-первых, плохо написанные регулярные выражения работают медленно.
Во-вторых, их зачастую очень сложно читать, особенно если регулярка написана не лично тобой пять минут назад.
В-третьих, очень часто даже небольшое изменение задачи (того, что требуется найти) приводит к значительному изменению выражения.
Поэтому про регулярки часто говорят, что это write only code (код, который только пишут с нуля, но не читают и не правят).
Вот пример write-only регулярки (для проверки валидности e-mail адреса (не надо так делать!!!)):
(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|
2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])
А вот здесь более точная регулярка для проверки корректности email адреса стандарту RFC822. Если вдруг будете проверять email, то не делайте так! Если адрес вводит пользователь, то пусть вводит почти что угодно, лишь бы там были собака и точка. Надёжнее всего отправить туда письмо и убедиться, что пользователь может его получить.
Любая строка (в которой нет сециальных символов: .^$*+?{}[]\|()
) сама по себе является регулярным выражением. Так, выражению "Хаха" будет соответствовать строка “Хаха” и только она. Регулярные выражения являются регистрозависимыми, поэтому строка “хаха” (с маленькой буквы) уже не будет соответствовать выражению выше.
Регулярные выражения имеют спецсимволы .^$*+?{}[]\|()
, которые в регулярках являются управляющими конструкциями. Для написания их просто как символов требуется их экранировать, для чего нужно поставить перед ними знак "\
". Так же, как и везде, в регулярных выражения выражение \n
соответствует концу строки, а \t
— табуляции.
Во всех примерах ниже соответствия регулярному выражению выделяются курсивом.
Шаблон | Описание | Пример | Применяем к тексту |
---|---|---|---|
. | Один любой символ, кроме новой строки \n |
м.л.ко | молоко малако м0л0ко |
\d |
Любая цифра (Вообще говоря, в \d включается всё, что в юникоде помечено как «цифра») |
СУ\d\d | СУ35 СУ111 АЛСУ14 |
\D |
Любой символ, кроме цифры | 926\D123 |
926)123 1926-1234 |
\s |
Любой пробельный символ (пробел, табуляция, конец строки и т.п.) | бор\sода |
бор ода "бор ода" |
\S |
Любой непробельный символ | \S123 |
X123 я123 !123456 |
\w |
Любая буква (то, что может быть частью слова), а также цифры и знак "_" | \w\w\w |
Год f_3 qwert |
\W |
Любая не-буква, не-цифра и не подчёркивание | сом\W |
сом!, сом? |
[..] |
Один из символов в скобках, а также любой символ из диапазона a-b |
[0-9][0-9A-Fa-f] | 12 1F 4B |
[^..] |
Любой символ, кроме перечисленных | <[^>]> | <1> <a> <>> |
[а-яА-ЯёЁ] | Буква “ё” не включается в общий диапазон букв (\w )! Поэтому для указания полного диапазона кириллицы приходится писать расширенный диапазон |
||
[abc-] , [-1] |
если нужен знак минус, его нужно указать последним или первым (иначе он будет интерпретироваться как диапазон) | ||
[(+\\\]] |
внутри квадратных скобок нужно экранировать только закрывающую квадратную скобку "]" и знак "\ " |
||
\b |
Начало или конец слова (слева пусто или не-буква, справа буква и наоборот). В отличие от предыдущих соответствует позиции, а не символу |
\bвал |
вал перевал |
\B |
Не граница слова: либо и слева, и справа буквы, либо и слева, и справа НЕ буквы | \Bвал |
перевал вал |
Шаблон | Описание | Пример | Применяем к тексту |
---|---|---|---|
{n} |
Ровно "n" повторений | \d{4} |
1 12 123 1234 12345 |
{m,n} |
От "m" до "n" повторений включительно | \d{2,4} |
1 12 123 1234 12345 |
{m,} |
Не менее "m" повторений | \d{3,} |
1 12 123 1234 ... |
{,n} |
Не более "n" повторений | \d{,2} |
1 12 123 |
? |
Ноль или одно вхождение, синоним {0,1} |
вал? |
вал валы валов |
* |
Ноль или более вхождений, синоним {0,} |
СУ\d* |
СУ СУ1 СУ12... |
+ |
Одно или более, синоним {1,} |
a\)+ |
a) a)) a))) ba)]) |
*? +? ?? {m,n}? {,n}? {m,}? |
По умолчанию квантификаторы жадные - захватывают максимально возможное число символов. Добавление символа "?" делает их ленивыми, они захватывают минимально возможное число символов | \(.*\) |
(a+b)*(c+d)*(e+f) |
\(.*?\) |
(a+b)*(c+d)*(e+f) |
Есть некоторые метасимволы, которые мы еще не изучили. Большинство из них будут рассмотрены в этом разделе.
"|
"
Соответствует оператору ИЛИ. Если А и В являются регулярными выражениями, то A|B
будет соответствовать любая строка, которая соответствует А или В. Метасимвол "|" имеет очень низкий приоритет для того, чтобы заставить его работать разумно, когда вы чередуете несколько символов строки. Crow|Servo
будет искать соответствие либо Crow, либо Servo, а не Cro('w' или 'S')ervo
.
"^
"
Ищет соответствие только в начале строки. Если включен флаг MULTILINE
, как говорилось выше, то происходит сравнение и для каждой части после символа новой строки.
Например, если вы хотите найти только те строки, у которых в начале имеется From, то в регулярном выражении записывается ^From
.
"$
"
То же, что "^", но в конце строки, которая определяется либо, собственно по концу строки как таковому, либо по символу новой строки.
\A
Совпадение только в начале строки, то есть тоже, что "^", но не зависит от флага MULTILINE
\Z
Совпадение только в конце строки, то есть тоже, что "$", но не зависит от флага MULTILINE
\b
Граница слова. Слово определяется как последовательность символов чисел и/или букв, так что границы слова представляют пробелы или любые символы, не относящиеся к перечисленным.
Часто необходимо получить больше информации, чем просто узнать, находит ли регулярное выражение соответствие или нет. Регулярные выражения часто используются для разрезания строк написанием регулярных выражений, разделенных на несколько подгрупп, которые соответствуют различным компонентам запроса. Например, в стандарте RFC-822 в заголовке имеются различные поля, разделенные двоеточием:
From: author@example.com
User-Agent: Thunderbird 1.5.0.9 (X11/20061227)
MIME-Version: 1.0
To: editor@example.com
Это может быть обработано написанием регулярного выражения, которое соответствует всей строке заголовка, и в нем есть одна группа, которая соответствует имени заголовка, и другая группа, которая соответствует значению заголовка.
Группы обозначаются метасимволами в виде круглых скобок '(
', ')
'. '(
' и ')
' имеют такой же смысл, как в математических выражениях; они группируют вместе выражения, содержащиеся в них, и вы можете повторять содержание группы повторяющими квалификаторами, такими как *
, +
, ?
и {m, n}
. Например, (ab)*
будет соответствовать нулю или более повторений ab
.
Группы, определяемые скобками, также захватывают начальные и конечные индексы совпадающего текста. Группы нумеруются, начина с 0. Группа 0 имеется всегда, это само регулярное выражение целиком.
Подгруппы нумеруются слева направо, от 1 и далее. Группы могут быть вложенными; для того чтобы определить число вложений, просто подсчитываем слева направо символы открывающей скобки.
Обратные ссылки в шаблоне позволяют вам указать, что содержание ранее захваченной группы также должно быть найдено в текущей позиции строки. Например, \1
соответствует тому, что содержание группы 1
в точности повторяется в текущей позиции.
Например, следующая "регулярка" обнаруживает в строке дважды подряд повторяющиеся слова:
(\b\w+)\s+\1
Предположим, перед нами стоит задача — найти все HTML-теги в строке
<p><b>Tproger</b> — мой <i>любимый</i> сайт о программировании!</p>
Очевидное решение <.*>
здесь не сработает — оно найдёт всю строку целиком, т.к. она начинается с тега абзаца (<p>
) и им же заканчивается. То есть содержимым тега будет считаться строка:
p><b>Tproger</b> — мой <i>любимый</i> сайт о программировании!</p
Это происходит из-за того, что по умолчанию квантификатор работают по т.н. "жадному" алгоритму — старается вернуть как можно более длинную строку, соответствующую условию. Решить проблему можно двумя способами. Первый — использовать выражение <[^>]*>
, которое запретит считать содержимым тега правую угловую скобку. Второй — объявить квантификатор не "жадным", а "ленивым". Делается это с помощью добавления справа к квантификатору символа "?". Т.е. для поиска всех тегов выражение обратится в <.*?>
.
Основная функциональность регулярных выражений в .NET сосредоточена в пространстве имен System.Text.RegularExpressions. А центральным классом при работе с регулярными выражениями является класс Regex. Например, у нас есть некоторый текст и нам надо найти в нем все словоформы какого-нибудь слова. С классом Regex это сделать очень просто:
string s = "Бык тупогуб, тупогубенький бычок, у быка губа бела была тупа";
Regex regex = new Regex(@"туп(\w*)");
MatchCollection matches = regex.Matches(s);
if (matches.Count > 0)
{
foreach (Match match in matches)
Console.WriteLine(match.Value);
}
else
{
Console.WriteLine("Совпадений не найдено");
}
Здесь мы находим в искомой строке все словоформы слова "туп". В конструктор объекта Regex передается регулярное выражение для поиска. Выражение туп(\w*)
обозначает, найти все слова, которые имеют корень "туп" и после которого может стоять различное количество символов. Выражение \w
означает алфавитно-цифровой символ, а звездочка после выражения указывает на неопределенное их количество - их может быть один, два, три или вообще не быть.
Метод Matches класса Regex принимает строку, к которой надо применить регулярные выражения, и возвращает коллекцию найденных совпадений.
Каждый элемент такой коллекции представляет объект Match. Его свойство Value возвращает найденное совпадение.
Метод Matches ищет все вхождения словоформы туп, но на практике чаще приходится искать одно совпадение с шаблоном, но разбитое на группы. Для этого больше подходит метод Match, который возвращает объект типа Match. У этого объекта есть свойство Success, по которому мы можем определить найдена ли строка по шаблону и массив Groups, в котором находятся значения подгрупп (в группе 0, напоминаю, находится вся найденная строка).
string text = "One car red car blue car";
string pat = @"(\w+)\s+(car)";
Regex r = new Regex(pat, RegexOptions.IgnoreCase);
Match m = r.Match(text);
int matchCount = 0;
// можно искать не одно вхождение, а несколько
while (m.Success)
{
Console.WriteLine("Match"+ (++matchCount));
// тут можно было бы перебирать по длине массива Groups,
// но мы по своему шаблону и так знаем, что у нас две подгруппы
for (int i = 1; i <= 2; i++)
{
Console.WriteLine($"Group {i}='{m.Groups[i]}'");
}
// поиск следующей подстроки соответсвующей шаблону
m = m.NextMatch();
}
Выведет
Match1
Group 1=One
Group 2=car
Match2
Group 1=red
Group 2=car
Match3
Group 1=blue
Group 2=car
Класс Regex имеет ряд конструкторов, позволяющих выполнить начальную инициализацию объекта. Две версии конструкторов в качестве одного из параметров принимают перечисление RegexOptions. Некоторые из значений, принимаемых данным перечислением:
Compiled: при установке этого значения регулярное выражение компилируется в сборку, что обеспечивает более быстрое выполнение
CultureInvariant: при установке этого значения будут игнорироваться региональные различия
IgnoreCase: при установке этого значения будет игнорироваться регистр
IgnorePatternWhitespace: удаляет из строки пробелы и разрешает комментарии, начинающиеся со знака #
Multiline: указывает, что текст надо рассматривать в многострочном режиме. При таком режиме символы "^" и "$" совпадают, соответственно, с началом и концом любой строки, а не с началом и концом всего текста
RightToLeft: приписывает читать строку справа налево
Singleline: устанавливает однострочный режим, а весь текст рассматривается как одна строка
Например:
Regex regex = new Regex(
@"туп(\w*)",
RegexOptions.IgnoreCase);
При необходимости можно установить несколько параметров:
Regex regex = new Regex(
@"туп(\w*)",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
Теперь посмотрим на некоторые примеры использования. Возьмем первый пример с скороговоркой "Бык тупогуб, тупогубенький бычок, у быка губа бела была тупа" и найдем в ней все слова, где встречается корень "губ":
string s = "Бык тупогуб, тупогубенький бычок, у быка губа бела была тупа";
Regex regex = new Regex(@"\w*губ\w*");
Так как выражение \w*
соответствует любой последовательности алфавитно-цифровых символов любой длины, то данное выражение найдет все слова, содержащие корень "губ".
Второй простенький пример - нахождение телефонного номера в формате 111-111-1111
:
string s = "456-435-2318";
Regex regex = new Regex(
@"\d{3}-\d{3}-\d{4}");
Если мы точно знаем, сколько определенных символов должно быть, то мы можем явным образом указать их количество в фигурных скобках: \d{3}
- то есть в данном случае три цифры.
Мы можем не только задать поиск по определенным типам символов - пробелы, цифры, но и задать конкретные символы, которые должны входить в регулярное выражение. Например, перепишем пример с номером телефона и явно укажем, какие символы там должны быть:
string s = "456-435-2318";
Regex regex = new Regex(
"[0-9]{3}-[0-9]{3}-[0-9]{4}");
В квадратных скобках задается диапазон символов, которые должны в данном месте встречаться. В итоге данный и предыдущий шаблоны телефонного номера будут эквивалентны.
Также можно задать диапазон для алфавитных символов: Regex regex = new Regex("[a-v]{5}");
- данное выражение будет соответствовать любому сочетанию пяти символов, в котором все символы находятся в диапазоне от a
до v
.
Можно также указать отдельные значения: Regex regex = new Regex(@"[2]*-[0-9]{3}-\d{4}");
. Это выражение будет соответствовать, например, такому номеру телефона "222-222-2222" (так как первые числа двойки)
С помощью операции |
можно задать альтернативные символы: Regex regex = new Regex(@"[2|3]{3}-[0-9]{3}-\d{4}");
. То есть первые три цифры могут содержать только двойки или тройки. Такой шаблон будет соответствовать, например, строкам "222-222-2222" и "323-435-2318". А вот строка "235-435-2318" уже не подпадает под шаблон, так как одной из трех первых цифр является цифра 5.
Итак, у нас такие символы, как *
, +
и ряд других используются в качестве специальных символов. И возникает вопрос, а что делать, если нам надо найти, строки, где содержится точка, звездочка или какой-то другой специальный символ? В этом случае нам надо просто экранировать эти символы слешем:
Regex regex = new Regex(
@"[2|3]{3}\.[0-9]{3}\.\d{4}");
// этому выражению будет соответствовать строка "222.222.2222"
Нередко возникает задача проверить корректность данных, введенных пользователем. Это может быть проверка электронного адреса, номера телефона, Класс Regex предоставляет статический метод IsMatch, который позволяет проверить входную строку с шаблоном на соответствие:
string pattern = @"^(?("")(""[^""]+?""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" +
@"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9]{2,17}))$";
while (true)
{
Console.WriteLine("Введите адрес электронной почты");
string email = Console.ReadLine();
if (Regex.IsMatch(email, pattern, RegexOptions.IgnoreCase))
{
Console.WriteLine("Email подтвержден");
break;
}
else
{
Console.WriteLine("Некорректный email");
}
}
Переменная pattern задает регулярное выражение для проверки адреса электронной почты. Данное выражение предлагает нам Microsoft на страницах msdn.
Для проверки соответствия строки шаблону используется метод IsMatch: Regex.IsMatch(email, pattern, RegexOptions.IgnoreCase)
. Последний параметр указывает, что регистр можно игнорировать. И если введенная строка соответствует шаблону, то метод возвращает true.
Класс Regex имеет метод Replace, который позволяет заменить строку, соответствующую регулярному выражению, другой строкой:
string s = "Мама мыла раму. ";
string pattern = @"\s+";
string target = " ";
Regex regex = new Regex(pattern);
string result = regex.Replace(s, target);
Данная версия метода Replace принимает два параметра: строку с текстом, где надо выполнить замену, и сама строка замены. Так как в качестве шаблона выбрано выражение "\s+ (то есть наличие одного и более пробелов), метод Replace проходит по всему тексту и заменяет несколько подряд идущих пробелов ординарными.
Реализовать примеры из лекции.
Лабораторную по теме "строки" реализовать через регулярки (где это возможно)
Предыдущая лекция | Следующая лекция | |
---|---|---|
Типы файлов: CSV, XML, JSON. | Содержание | ООП. Базовые понятия. |