Введение в регулярные выражения в современном C++
Привет, Хабр. Будущих студентов курса «C++ Developer. Professional» приглашаем записаться на открытый урок по теме «Backend на современном C++»
А пока делимся традиционным переводом полезного материала.
Регулярные выражения (Regular expressions или, вкратце, regex — регулярки) — это пока что непопулярная и недооцененная тема в современном C++. Но в то же время разумное использование регулярных выражений может избавить вас от написания множества строчек кода. Если у вас уже есть какой-никакой опыт работы в индустрии, но вы не умеете использовать регулярные выражения — вы разбазариваете 20-30% своей продуктивности. Я настоятельно рекомендую вам освоить регулярные выражение, так как это единовременная инвестиция в себя (по известному принципу “learn once, write anywhere”).
/!\: Изначально эта статья была опубликована в моем личном блоге. Если вам станет интересно и дальше читать мои самые актуальные статьи, вы можете подписаться на мою рассылку.
Изначально я хотел включить в эту статью сведения о регулярных выражениях вообще в целом. Но это не имеет особого смысла, так как уже существует множество людей и учебников, которые намного лучше меня вводят в регулярные выражения. Но все же я оставил небольшую часть, посвященную мотивации и изучению регулярок. В оставшейся части статьи я сосредоточусь на функциональности для работы с регулярным выражением, предоставляемой конкретно C++. И если вы уже имеете представление о регулярных выражениях, вы можете использовать приведенную ниже ассоциативную карту в качестве напоминания.
Примечание: стандартная библиотека C++ предлагает несколько различных разновидностей («flavours») синтаксиса регулярных выражений, но вариант по умолчанию (тот, который вы всегда должны использовать, и который я демонстрирую здесь) был полностью позаимствован из стандарта ECMAScript.
Мотивация
Я понимаю, инструментарий регулярок скуден и достаточно запутан. Рассмотрим приведенный ниже шаблон регулярного выражения, который извлекает время в 24-часовом формате (т.е. ЧЧ:ММ), в качестве примера.
\b([01]?[0-9]|2[0-3]):([0-5]\d)\b
Вот да! Кто захочет возиться с этим непонятным текстом?
И все, что приходит вам в голову, глядя на это, на 100% небезосновательно. Я сам дважды откладывал изучение регулярных выражений по той же причине. Но, поверьте, этот неприглядный инструмент не так уж и плох.
Подход (↓), который я здесь описываю, не отнимет у вас больше 2-3 часов на изучение регулярных выражений, которые, по правде говоря, интуитивно понятны. После того, как вы их освоите, вы увидите, что с течением времени ваша инвестиция дает стабильные дивиденды.
Изучение регулярных выражений
Не нужно много гуглить и пытаться анализировать, какой учебник лучше. Вообще не тратьте время на такой анализ. Потому что в этом нет смысла. На данный момент (ну, если вы еще не знаете регулярные выражения) больше смысла имеет не пытаться угадать, где же лучше начать, а собственно начать изучение.
После этого, если у вас осталось желание порешать побольше задач и упражнений, рассмотрите приведенные ниже ссылки:
- Упражнения на regextutorials.com
- Практические задачи с регулярными выражениями на hackerrank
Пример std::regex и std::regexerror
int main() < try < static const auto r = std::regex(R"(\)"); // Escape sequence error >catch (const std::regex_error &e) < assert(strcmp(e.what(), "Unexpected end of regex when escaping.") == 0); assert(e.code() == std::regex_constants::error_escape); >return EXIT_SUCCESS; >
Вот видите! Я использую сырые строковые литералы. Вы также можете использовать обычную строку, но в таком случае вы должны использовать двойной бэкслеш (\) для escape-последовательности.
Текущая реализация std::regex медленная (так как требует интерпретации регулярных выражений и создания структуры данных во время выполнения), раздувается и неизбежно требует динамического выделения памяти (не allocator aware). Будьте осторожны, если вы используете std::regex в цикле (см. C++ Weekly — Ep 74 — std::regex optimize by Jason Turner). Кроме того, в ней есть только одна функция-член, которая, как я думаю, действительно может быть полезной — это std::regex::markcount() , которая возвращает несколько групп захвата.
Более того, если вы используете несколько строк для создания шаблона регулярного выражения во время выполнения, то вам может потребоваться обработка исключений (например, std::regexerror) , чтобы проверить его правильность.
Пример std::regex_search
int main() < const string input s; const regex r(R"((\w+):(\w+);)"); smatch m; if (regex_search(input, m, r)) < assert(m.size() == 3); assert(m[0].str() == "PQR:2;"); // Entire match assert(m[1].str() == "PQR"); // Substring that matches 1st group assert(m[2].str() == "2"); // Substring that matches 2nd group assert(m.prefix().str() = ); // All before 1st character match assert(m.suffix().str() == ";; XYZ:3<<<"); // All after last character match // for (string &&str : m) < // Alternatively. You can also do // cout << str << endl; // >> return EXIT_SUCCESS; >
smatch — это специализация std::match_results, которая хранит информацию о найденных совпадениях (матчах).
Пример std::regex_match
Лаконичный и приятный пример, который вы всегда можете найти в каждой книге о регулярках, — это валидация электронной почты. И именно здесь идеально подходит функция std::regexmatch .
bool is_valid_email_id(string_view str) < static const regex r(R"(\w+@\w+\.(?:com|in))"); return regex_match(str.data(), r); >int main()
return EXIT¨C14Cmatch , а не std::regex¨C15Cmatch сопоставляет (матчит) всю входную последовательность.
Еще одна примечательная вещь — это статический объект регулярного выражения (static const regex), чтобы избежать создания («компиляции/интерпретации») нового объекта регулярного выражения каждый раз при заходе в функцию.
Вся ирония этого крошечного фрагмента кода заключается в том, что он генерирует порядка 30 тысяч строк сборки с флагом -O3. И это просто смешно. Но не волнуйтесь, это уже было доведено до комитета ISO C++. И в скором времени мы можем получить обновление, устраняющее проблему. Между тем у нас есть и другие альтернативы (упомянутые в конце этой статьи).
Разница между std::regex_match и std::regex_search
Вам может быть интересно, почему у нас есть две функции, выполняющие почти одинаковую работу? Даже я изначально не понимал этого. Но после многократного прочтения описания, предоставляемого cppreference, я нашел ответ. И чтобы объяснить этот ответ, я создал пример (очевидно, не без помощи StackOverflow):
int main()
std::regexmatch возвращает true только тогда, когда совпадает вся входная последовательность, в то время как std::regexsearch вернет true , даже если только часть последовательности соответствует регулярному выражению.
Пример std::regex_iterator
std::regex_iterator полезен, когда требуется очень подробная информация о соответствиях и сабматчах.
#define C_ALL(X) cbegin(X), cend(X) int main() < const string input s; const regex r(R"((\w+):(\d))"); const vectormatches< sregex_iterator, sregex_iterator<> >; assert(matches[0].str(0) == "ABC:1" && matches[0].str(1) == "ABC" && matches[0].str(2) == "1"); assert(matches[1].str(0) == "PQR:2" && matches[1].str(1) == "PQR" && matches[1].str(2) == "2"); assert(matches[2].str(0) == "XYZ:3" && matches[2].str(1) == "XYZ" && matches[2].str(2) == "3"); return EXIT_SUCCESS; >
Ранее (в C++11) существовало ограничение, заключающееся в том, что std::regex_interator нельзя было вызывать с временным объектом регулярного выражения. Что было исправлено с помощью перегрузки в C++14.
Пример std::regex_token_iterator
std::regextokeniterator — это утилита, которую вы будете использовать в 80% случаев. Она немного отличается от std::regexiterator . Разница между td::regexiterator и std::regextokeniterator заключается в том, что
- std::regexiterator указывает на соответствующие результаты.
- std::regextokeniterator указывает на сабматчи.
В std::regextoken_iterator каждый итератор содержит только один соответствующий результат.
#define C_ALL(X) cbegin(X), cend(X) int main() < const string input s; const regex r(R"((\w+):(\d))"); // Note: vectorhere, unlike vector as in std::regex_iterator const vector full_match< sregex_token_iterator, // Mark `0` here i.e. whole regex match sregex_token_iterator<> >; assert((full_match == decltype(full_match))); const vector cptr_grp_1st< sregex_token_iterator, // Mark `1` here i.e. 1st capture group sregex_token_iterator<> >; assert((cptr_grp_1st == decltype(cptr_grp_1st))); const vector cptr_grp_2nd< sregex_token_iterator, // Mark `2` here i.e. 2nd capture group sregex_token_iterator<> >; assert((cptr_grp_2nd == decltype(cptr_grp_2nd))); return EXIT_SUCCESS; >
Инверсия соответствия с std::regex_token_iterator
#define C_ALL(X) cbegin(X), cend(X) int main() < const string input s; const regex r(R"((\w+):(\d))"); const vectorinverted< sregex_token_iterator, // `-1` = parts that are not matched sregex_token_iterator<> >; assert((inverted == decltype(inverted) < "", "->", ";;; ", ")); return EXIT_SUCCESS; >
Пример std::regex_replace
string transform_pair(string_view text, regex_constants::match_flag_type f = <>) < static const auto r = regex(R"((\w+):(\d))"); return regex_replace(text.data(), r, "$2", f); >int main() < assert(transform_pair("ABC:1, PQR:2"s) == "1, 2"s); // Things that aren't matched are not copied assert(transform_pair("ABC:1, PQR:2"s, regex_constants::format_no_copy) == "12"s); return EXIT_SUCCESS; >
Вы видите, что во втором вызове transformpair мы передали флаг std::regexconstants::formatnocopy , который говорит не копировать те части, которые не соответствуют регулярке. В std::regexconstant есть много подобных полезных флагов.
Кроме того, мы создали новую строку, содержащую результаты. Но что, если нам не нужна новая строка, а нужно добавить результаты куда-нибудь, возможно, в контейнер, поток или уже существующую строку. Угадайте, что! Стандартная библиотека уже реализует такую потребность также с помощью перегруженного std::regexreplace следующим образом:
int main() < const string input s; const regex r(R"(-|>|<|;| )"); // Prints "ABC:1 PQR:2 XYZ:3 " regex_replace(ostreambuf_iterator(cout), C_ALL(input), r, " "); return EXIT_SUCCESS; >
Примеры использования
Разделение строки с помощью разделителя (delimiter)
std::strtok является наиболее подходящим и оптимальным кандидатом для такой задачи, но в целях демонстрации продемонстрируем как это можно сделать с помощью регулярного выражения:
#define C_ALL(X) cbegin(X), cend(X) vector split(const string& str, string_view pattern) < const auto r = regex(pattern.data()); return vector< sregex_token_iterator(C_ALL(str), r, -1), sregex_token_iterator() >; > int main() < assert((split("/root/home/vishal", "/") == vector)); return EXIT_SUCCESS; >
Удаление пробелов из строки
string trim(string_view text) < static const auto r = regex(R"(\s+)"); return regex_replace(text.data(), r, ""); >int main()
Поиск строк, содержащих или НЕ содержащих определенные слова, из файла
string join(const vector& words, const string& delimiter) < return accumulate(next(begin(words)), end(words), words[0], [&delimiter](string& p, const string& word) < return p + delimiter + word; >); > vector lines_containing(const string& file, const vector& words) < auto prefix = "^.*?\\b("s; auto suffix = ")\\b.*$"s; // ^.*?\b(one|two|three)\b.*$ const auto pattern = move(prefix) + join(words, "|") + move(suffix); ifstream infile(file); vectorresult; for (string line; getline(infile, line);) < if(regex_match(line, regex(pattern))) < result.emplace_back(move(line)); >> return result; > int main() < assert((lines_containing("test.txt", ) == vector)); return EXIT_SUCCESS; > /* test.txt This is one This is two This is three This is four */
То же самое касается поиска строк, которые не содержат слов с шаблоном ^((?!(one|two|three)).)*$ .
Поиск файлов в папке
namespace fs = std::filesystem; vector find_files(const fs::path &path, string_view rg) < vectorresult; regex r(rg.data()); copy_if( fs::recursive_directory_iterator(path), fs::recursive_directory_iterator(), back_inserter(result), [&r](const fs::directory_entry &entry) < return fs::is_regular_file(entry.path()) && regex_match(entry.path().filename().string(), r); >); return result; > int main() < const auto dir = fs::temp_directory_path(); const auto pattern = R"(\w+\.png)"; const auto result = find_files(fs::current_path(), pattern); for (const auto &entry : result) < cout return EXIT_SUCCESS; >
Общие советы по использованию регулярных выражений
- Для описания шаблона регулярного выражения в C++ лучше используйте сырые строковые литералы.
- Пользуйтесь инструментом проверки регулярных выражений, например https://regex101.com. Что мне нравится в regex101, так это функция генерации кода и вычисление затраченного времени (будет полезна при оптимизации регулярного выражения).
- Кроме того, хорошим тоном будет добавление объяснения, сгенерированного инструментом проверки, в виде комментария над шаблоном регулярного выражения в вашем коде. Производительность:
- Если вы используете чередование (alternation), попробуйте расположить параметры в порядке наивысшей вероятности, например com|net|org .
- Старайтесь использовать ленивые квантификаторы.
- По возможности используйте группы без захвата.
- Отключайте бэктрекинг.
- Использование отрицательного класса символов более эффективно, чем использование ленивой точки.
Заключение
Дело не в том, будете ли вы использовать регулярные выражения исключительно на C++ или любым другим языком. Я сам использую их в основном в IDE (в vscode для анализа файлов логов) и на терминале Linux. Имейте в виду, что чрезмерное использование регулярных выражений дает чрезмерное ощущение собственной сообразительности. И это отличный способ рассердить на вас ваших коллег (и всех, кому нужно работать с вашим кодом). Кроме того, регулярные выражения являются излишними для большинства задач синтаксического анализа, с которыми вы столкнетесь в своей повседневной работе.
Регулярные выражения действительно подходят для сложных задач, где рукописный код синтаксического анализа в любом случае будет столь же медленным; и для чрезвычайно простых задач, когда удобочитаемость и надежность регулярных выражений перевешивают их затраты на производительность.
Еще одна примечательная вещь — текущая реализация регулярных выражений (до 19 июня 2020 года) в стандартных библиотеках имеет проблемы с производительностью и раздутием кода. Так что выбирайте с умом между версиями библиотек Boost, CTRE и Std. Скорее всего, вы согласитесь с работой Ханы Дусиковой над регулярным выражением времени компиляции. Кроме того, ее выступление на CppCon в 2018 и 2019 было бы для вас полезно, особенно если вы планируете использовать регулярное выражение во встроенных системах.
Какие есть регулярные выражения в Си?
Здравствуйте! В переменной есть текст, в котором нужно найти информацию. Я планирую это сделать с помощью регулярных выражений. Столкнулся с проблемой при написании на Си. Заголовочный файл
в MS VS 2010 отсутствует, есть файл , но работать с ним нельзя, так как он предназначен для C++, а я пишу на C(Си). Подскажите, есть какая-то альтернативная библиотека/заголовочный файл или способ установки файла в VS? Спасибо! PS Я так уперто пишу на Си, так как хочу перейти потом на objective-c. Или можно спокойно переходить на C++, так как это не повлечёт проблем при написании на objective-c? Или я ошибаюсь? ЗЗЫ Уважаемый @ХэшКод, подскажите, пожалуйста, почему у меня пропала кнопка «добавить комментарий» — я не могу ответить на вопросы? Отслеживать
ВладиславМСК
задан 22 июл 2012 в 17:39
ВладиславМСК ВладиславМСК
1,541 11 11 золотых знаков 38 38 серебряных знаков 59 59 бронзовых знаков
А просто запихнуть в проект regex.h почему не хотите?
22 июл 2012 в 17:41
@knes, а вы уверены, что там нет каких-то ещё сторонних которых тоже не будет в VS 2010?
22 июл 2012 в 17:47
> так как это не повлечёт проблем при написании на objective-c Умение мыслить — вот то, что избавит проблем от написания на любом ЯП. Я сразу с Паскаля перешел.
22 июл 2012 в 17:48
@VioLet, ну как успехи? Есть свои приложения в AppStore?
Шпаргалка по регулярным выражениям. В примерах
Регулярные выражения (regex или regexp) очень эффективны для извлечения информации из текста. Для этого нужно произвести поиск одного или нескольких совпадений по определённому шаблону (т. е. определённой последовательности символов ASCII или unicode).
Области применения regex разнообразны, от валидации до парсинга/замены строк, передачи данных в другие форматы и Web Scraping’а.
Одна из любопытных особенностей регулярных выражений в их универсальности, стоит вам выучить синтаксис, и вы сможете применять их в любом (почти) языке программирования (JavaScript, Java, VB, C #, C / C++, Python, Perl, Ruby, Delphi, R, Tcl, и многих других). Небольшие отличия касаются только наиболее продвинутых функций и версий синтаксиса, поддерживаемых движком.
Давайте начнём с нескольких примеров.
Основы
Якоря — ^ и $
^Привет соответствует строке, начинающейся с Привет -> тестпока$ соответствует строке, заканчивающейся на пока^Привет пока$ точное совпадение (начинается и заканчивается как Привет пока)воробушки соответствует любой строке, в которой есть текст воробушки
Квантификаторы — * + ? и <>
abc* соответствует строке, в которой после ab следует 0 или более символов c -> тестabc+ соответствует строке, в которой после ab следует один или более символов cabc? соответствует строке, в которой после ab следует 0 или один символ cabc соответствует строке, в которой после ab следует 2 символа cabc соответствует строке, в которой после ab следует 2 или более символов cabc соответствует строке, в которой после ab следует от 2 до 5 символов ca(bc)* соответствует строке, в которой после ab следует 0 или более последовательностей символов bca(bc) соответствует строке, в которой после ab следует от 2 до 5 последовательностей символов bc
Оператор ИЛИ — | или []
a(b|c) соответствует строке, в которой после a следует b или c -> тестa[bc] как и в предыдущем примере
Символьные классы — \d \w \s и .
\d соответствует одному символу, который является цифрой -> тест\w соответствует слову (может состоять из букв, цифр и подчёркивания) -> тест\s соответствует символу пробела (включая табуляцию и прерывание строки). соответствует любому символу -> тест
Используйте оператор . с осторожностью, так как зачастую класс или отрицаемый класс символов (который мы рассмотрим далее) быстрее и точнее.
У операторов \d , \w и \s также есть отрицания ― \D, \W и \S соответственно.
Например, оператор \D будет искать соответствия противоположенные \d .
Непечатаемые символы также можно искать, например табуляцию \t , новую строку \n , возврат каретки \r .
Флаги
Мы научились строить регулярные выражения, но забыли о фундаментальной концепции ― флагах.
Регулярное выражение, как правило, записывается в такой форме / abc /, где шаблон для сопоставления выделен двумя слешами / . В конце выражения, мы определяем значение флага (эти значения можно комбинировать):
- g (global) ― не возвращает результат после первого совпадения, а продолжает поиск с конца предыдущего совпадения.
- m (multi line) ― с таким флагом, операторы ^ и $ вызовут совпадение в начале и конце строки ввода (line), вместо строки целиком (string).
- i (insensitive) ― делает выражение регистронезависимым (например, /aBc/i соответствует AbC).
Средний уровень
Скобочные группы ― ()
a(bc) создаём группу со значением bc -> тестa(?:bc)* оперетор ?: отключает группу -> тестa(?bc) так, мы можем присвоить имя группе -> тест
Этот оператор очень полезен, когда нужно извлечь информацию из строк или данных, используя ваш любимый язык программирования. Любые множественные совпадения, по нескольким группам, будут представлены в виде классического массива: доступ к их значениям можно получить с помощью индекса из результатов сопоставления.
Если присвоить группам имена (используя (? . ) ), то можно получить их значения, используя результат сопоставления, как словарь, где ключами будут имена каждой группы.
Скобочные выражения ― []
[abc] соответствует строке, которая содержит либо символ a или a b или a c -> такой же эффект от a|b|c -> тест[a-c] то же, что и выше[a-fA-F0–9] строка, представляющая одну шестнадцатеричную цифру без учёта регистра -> тест[0–9]% строка, содержащая символ от 0 до 9 перед знаком %[^a-zA-Z] строка, которая не имеет буквы от a до z или от A до Z. В этом случае ^ используется как отрицание в выражении -> тест
Помните, что внутри скобочных выражений все специальные символы (включая обратную косую черту \ ) теряют своё служебное значение, поэтому нам ненужно их экранировать.
Жадные и ленивые сопоставления
Квантификаторы ( * + <> ) ― это «жадные» операторы, потому что они продолжают поиск соответствий, как можно глубже ― через весь текст.
Например, выражение <.+>соответствует
simple divв This is a
simple divtest . Чтобы найти только тэг div ― можно использовать оператор ? , сделав выражение «ленивым»:
Обратите внимание, что хорошей практикой считается не использовать оператор . , в пользу более строгого выражения:
Продвинутый уровень
Границы слов ― \b и \B
\b ― соответствует границе слова, наподобие якоря (он похож на $ и ^ ), где предыдущий символ ― словесный (например, \w ), а следующий ― нет, либо наоборот, (например, это может быть начало строки или пробел).
\B ― соответствует несловообразующей границе. Соответствие не должно обнаруживаться на границе \b .
Обратные ссылки — \1
([abc])\1 \1 соответствует тексту из первой захватываемой группы -> тест([abc])([de])\2\1 можно использовать \2 (\3, \4, и т.д.) для определения порядкового номера захватываемой группы -> тест(? [abc])\k мы присвоили имя foo группе, и теперь ссылаемся на неё используя ― (\k ). Результат, как и в первом выражении -> тест
Опережающие и ретроспективные проверки — (?=) and (?<=)
d(?=r) соответствует d, только если после этого следует r, но r не будет входить в соответствие выражения -> тест(?r)d соответствует d, только если перед этим есть r, но r не будет входить в соответствие выражения -> тест
Вы можете использовать оператор отрицания !
d(?!r) соответствует d, только если после этого нет r, но r не будет входить в соответствие выражения -> тест(?r)d соответствует d, только если перед этим нет r, но r не будет входить в соответствие выражения -> тест
Заключение
Как вы могли убедиться, области применения регулярных выражений разнообразны. Я уверен, что вы сталкивались с похожими задачами в своей работе (хотя бы с одной из них), например такими:
- Валидация данных (например, правильно ли заполнена строка time)
- Сбор данных (особенно веб-скрапинг, поиск страниц, содержащих определённый набор слов в определённом порядке)
- Обработка данных (преобразование сырых данных в нужный формат)
- Парсинг (например, достать все GET параметры из URL или текст внутри скобок)
- Замена строк (даже во время написания кода в IDE, можно, например преобразовать Java или C# класс в соответствующий JSON объект, заменить “;” на “,”, изменить размер букв, избегать объявление типа и т.д.)
- Подсветка синтаксиса, переименование файла, анализ пакетов и многие другие задачи, где нужно работать со строками (где данные не должны быть текстовыми).
Регулярные выражения
Регулярные выражения — это шаблоны, используемые для сопоставления последовательностей символов в строках. В JavaScript регулярные выражения также являются объектами. Эти шаблоны используются в методах exec (en-US) и test (en-US) объекта RegExp (en-US) а также match (en-US) , replace , search (en-US) , split (en-US) объекта String . Данная глава описывает регулярные выражения в JavaScript.
Создание регулярного выражения
Регулярное выражение можно создать двумя способами:
-
Используя литерал регулярного выражения, например:
var re = /ab+c/;
var re = new RegExp("ab+c");
Написание шаблона регулярного выражения
Шаблон регулярного выражения состоит из обычных символов, например /abc/ , или комбинаций обычных и специальных символов, например /ab*c/ или /Chapter (\d+)\.\d*/ . Последний пример включает в себя скобки, которые используются как «запоминающий механизм». Соответствие этой части шаблона запоминается для дальнейшего использования, как описано в Использование совпадений подстрок заключённых в скобки.
Использование простых шаблонов
Простые шаблоны используются для нахождения прямого соответствия в тексте. Например, шаблон /abc/ соответствует комбинации символов в строке только когда символы ‘abc’ встречаются вместе и в том же порядке. Такое сопоставление произойдёт в строке «Hi, do you know your abc’s?» и «The latest airplane designs evolved from slabcraft.» В обоих случаях сопоставление произойдёт с подстрокой ‘abc’. Сопоставление не произойдёт в строке «Grab crab», потому что она не содержит подстроку ‘abc’.
Использование специальных символов
В случае когда поиск соответствия требует чего-то большего, чем прямое сопоставление, например нахождение последовательности символов ‘b’ или нахождение пробела, шаблон включает в себя специальные символы. Например, шаблон /ab*c/ соответствует любой комбинации символов, в которой за ‘a’ следует ноль или более символов ‘b’ ( * означает ноль или более вхождений предыдущего символа), за которыми сразу же следует символ ‘c’. В строке «cbbabbbbcdebc,» этому шаблону сопоставляется подстрока ‘abbbbc’.
В следующей таблице приводится полный список специальных символов регулярных выражений с их описаниями.
- Для символов обычно обрабатываемых буквально, означает что следующий символ является специальным и не должен интерпретироваться буквально.
- Например, /b/ сопоставляется символу ‘b’. Добавляя слеш перед b, т.е используя /\b/ , символ становится специальным символом, означающим границу слова.
- Для символов обычно обрабатываемых особым образом означает, что следующий символ не является специальным и должен интерпретироваться буквально.
- Например, * является специальным символом, сопоставляемым 0 или более повторений предыдущего символа; например, /a*/ означает соответствие 0 или более символов а. Для буквальной интерпретации *, поставьте перед ней обратный слеш; например, /a\*/ соответствует ‘a*’.
- Также не забудьте заэкранировать сам \ при его использовании в записи new RegExp(«pattern») поскольку \ также является экранирующим символом в обычных строках.
Соответствует началу ввода. Если установлен флаг многострочности, также производит сопоставление непосредственно после переноса строки.
Например, /^A/ не соответствует ‘A’ в «an A», но соответствует ‘A’ в «An E».
Этот символ имеет другое значение при появлении в начале шаблона набора символов.
Например, /[^a-z\s]/ соответствует ‘I’ в «I have 3 sisters».
Соответствует концу ввода. Если установлен битовый флаг многострочности, также сопоставляется содержимому до переноса строки.
Например, /t$/ не соответствует ‘t’ в строке «eater», но соответствует строке «eat».
Соответствует предыдущему символу повторенному 0 или более раз. Эквивалентно .
Например, /bo*/ соответствует ‘boooo’ в «A ghost booooed» и ‘b’ в «A bird warbled», но не в «A goat grunted».
Соответствует предыдущему символу повторенному 1 или более раз. Эквивалентно .
Например, /a+/ соответствует ‘a’ в «candy» и всем символам ‘a’ в «caaaaaaandy».
0 или 1 раз. Эквивалентно .
Например, /e?le?/ соответствует ‘el’ в «angel» и ‘le’ в «angle» а также ‘l’ в «oslo».
Если использован сразу после квалификаторов * , + , ? , или <> , делает квалификатор «нежадным» (соответствующим минимальному количеству символов), в отличие от режима по умолчанию, являющимся «жадным» (соответствующим максимальному числу символов). Например, используя /\d+/ не глобальное сопоставление «123abc» возвращает «123», если использовать /\d+?/, только «1» будет возвращена.
Также используется в упреждающих утверждениях (assertions), описанных в строках x(?=y) и x(?!y) данной таблицы.
(десятичная точка) соответствует любому символу кроме переноса строки.
Например, /.n/ соответствует ‘an’ и ‘on’ в «nay, an apple is on the tree», но не ‘nay’.
Соответствует ‘x’ и запоминает это соответствие. Это называется захватывающие скобки.
Например, /(foo)/ соответствует ‘foo’ в «foo bar.» Сопоставленная строка может быть получена из элементов результирующего массива [1] , . [n] .
Соответствует ‘x’ только если за ‘x’ следует ‘y’. Это называется упреждение.
Например, /Jack(?=Sprat)/ соответствует ‘Jack’ только если за ним следует ‘Sprat’. /Jack(?=Sprat|Frost)/ соответствует ‘Jack’ только если за ним следует ‘Sprat’ или ‘Frost’. Тем не менее, ни ‘Sprat’ ни ‘Frost’ не являются частью сопоставленного результата.
Соответствует ‘x’ только если за ‘x’ не следует ‘y’. Это называется отрицательное упреждение.
Например, /\d+(?!\.)/ соответствует числу только если за ним не следует десятичная точка. Регулярное выражение /\d+(?!\.)/.exec(«3.141») сопоставит ‘141’ но не ‘3.141’.
Соответствует либо ‘x’ либо ‘y’.
Например, /green|red/ соответствует ‘green’ в «green apple» и ‘red’ в «red apple.»
n — положительное целое. Соответствует ровно n вхождениям предыдущего символа.
Например, /a/ не соответствует ‘a’ в «candy,» но соответствует всем а в «caandy,» первым двум а в «caaandy.»
m и n — положительные целые. Соответствует как минимум n и максимум m вхождениям предыдущего символа. При m=n=1 пропускается.
Например, /a/ ничему не соответствует в строке «cndy», символу ‘a’ в «candy,» двум а в «caandy,» и трём первым а в «caaaaaaandy». Отметим, что при сопоставлении «caaaaaaandy», совпадает «aaa», хотя изначальная строка содержит больше а.
Набор символов. Соответствует любому символу из перечисленных. Можно указать диапазон символов, используя тире. Специальные символы (как точка ( . ) и звёздочка ( * )) не имеют специального значения внутри такого набора. Их не надо экранировать. Экранирование работает также.
Например, [abcd] эквивалентна [ a-d] . Они соответствуют ‘b’ в «brisket» и ‘c’ в «city». /[a-z.]+/ и /[\w.]+/ обе соответствуют всему в «test.i.ng».
Инвертированный или дополняющий набор символов. Это означает соответствие всему, что не в скобках. Можно указать диапазон символов с помощью тире. Все, что действует в обычном наборе символов, действует и здесь.
Например, [^abc] эквивалентно [^a-c] . Они соответствуют изначально ‘r’ в «brisket» и ‘h’ в «chop.»
Соответствует границе слова. Граница слова соответствует позиции, где за символом слова не следует другой символ слова или предшествует ему. Отметим, что граница слова не включается в соответствие. Другими словами, длина сопоставленной границы слова равна нулю. (Не путать с [\b] .)
Примеры:
/\bmoo/ соответствует ‘moo’ в слове «moon» ;
/oo\b/ не соответствует ‘oo’ в слове «moon», поскольку за ‘oo’ следует символ ‘n’ , являющимся символом слова;
/oon\b/ соответствует ‘oon’ в слове «moon», поскольку ‘oon’ является окончанием строки, и таким образом, за этими символами не следует другой символ слова;
/\w\b\w/ никогда не будет ничему соответствовать, поскольку за символом слова никогда не может следовать и граница слова, и символ слова.Note: JavaScript’s regular expression engine defines a specific set of charactersto be «word» characters. Any character not in that set is considered a word break. This set of characters is fairly limited: it consists solely of the Roman alphabet in both upper- and lower-case, decimal digits, and the underscore character. Accented characters, such as «é» or «ü» are, unfortunately, treated as word breaks.
Соответствует несловообразующей границе. Несловообразующая граница соответствует позиции, в которой предыдущий и следующий символы являются символами одного типа: либо оба должны быть словообразующими символами, либо несловообразующими. Начало и конец строки считаются несловообразующими символами.
Например, /\B../ соответствует ‘oo’ в слове «noonday» (, а /y\B./ соответствует ‘ye’ в «possibly yesterday.»
Где X является символом случайного выбора из последовательности от А до Я. Соответствует управляющему символу в строке.
Например, /\cM/ соответствует control-M (U+000D) в строке.
Соответствует цифровому символу. Эквивалентно выражению [0-9] .
Например, /\d/ or /[0-9]/ соответствует ‘2’ в «B2 is the suite number.»
Соответствует любому нецифровому символу. Эквивалентно выражению [^0-9] .
Например, /\D/ or /[^0-9]/ соответствует ‘B’ в предложении «B2 is the suite number.»
Соответствует символу прогона страницы (U+000C). Особый символ управления печатью.
Соответствует одиночному символу пустого пространства, включая пробел, табуляция, прогон страницы, перевод строки. Эквивалентен [ \f\n\r\t\v\u00A0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u2028\u2029\u202f\u205f\u3000] .
Например, /\s\w*/ совпадает с ‘ bar’ в «foo bar.»
Соответствует одиночному символу непустого пространства. Эквивалентен [^ \f\n\r\t\v\u00A0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000] .
Например, /\S\w*/ совпадает с ‘foo’ в «foo bar.»
Соответствует любому цифробуквенному символу включая нижнее подчёркивание. Эквивалентен [A-Za-z0-9_] .
Например, /\w/ совпадает с ‘a’ в «apple,» ‘5’ в «$5.28,» и ‘3’ в «3D.»
Соответствует любому не цифробуквенному символу. Равносилен [^A-Za-z0-9_] .
Например, /\W/ or /[^A-Za-z0-9_]/ совпадает с ‘%’ в «50%.»
Где n это положительное целое, обратная ссылка на последнюю найденную подстроку, соответствующую n , заключённую в круглые скобки в регулярном выражении (считая левые скобки).
Например, /apple(,)\sorange\1/ соответствует ‘apple, orange,’ в «apple, orange, cherry, peach.»
Экранирование пользовательского ввода, соответствующего буквенной строке внутри регулярного выражения, может быть достигнуто простой заменой:
function escapeRegExp(string) return string.replace(/[.*+?^$<>()|[\]\\]/g, "\\$&"); // $& means the whole matched string >
Использование скобок
Скобки вокруг любой части регулярного выражения означают что эта часть совпадаемой подстроки будет запомнена. Раз запомнена, подстрока может выбрана для использования как это описано в Using Parenthesized Substring Matches.
Например, паттерн /Chapter (\d+)\.\d*/ включает в себя дополнительные экранирующие и специальные символы и указывает на то, что часть шаблона должна быть запомнена. Он точно соответствует символам слова ‘Chapter ‘, за которыми следует один или более цифровых символов ( \d означает любой цифровой символ, а ‘ +’ означает 1 или более раз), за которым следует десятичная точка (сама по себе являющаяся специальным символом; предшествующий десятичной точке слеш ‘ \’ означает, что паттерн должен искать литеральный символ ‘.’), после которой следует любой цифровой символ 0 или более раз (‘ \d’ обозначает цифровой символ, ‘ *’ обозначает 0 или более раз). Кроме того, круглые скобки используются для запоминания первых же совпавших цифровых символов.
Этот шаблон будет найден во фразе «Open Chapter 4.3, paragraph 6» и цифра ‘4’ будет запомнена. Но он не будет найден во фразе «Chapter 3 and 4», поскольку эта строка не имеет точки после цифры ‘3’.
Для того, чтобы сопоставить подстроку без вызова совпавшей части для запоминания, внутри круглых скобок необходимо предварить паттерн сочетанием символов ‘ ?:’ . Например, шаблон (?:\d+) будет соответствовать одному или более цифровому символу, но не запомнит совпавших символов.
Работа с Регулярными Выражениями
Регулярные выражения используются в методах test и exec объекта RegExp и с методами match , replace , search , и split объекта String . Эти методы подробно объясняются в Справочнике JavaScript
Метод Описание exec (en-US) Метод RegExp, который выполняет поиск совпадения в строке. Он возвращает массив данных. test (en-US) Метод RegExp , который тестирует совпадение в строке. Возвращает либо истину либо ложь. match (en-US) Метод String , который выполняет поиск совпадения в строке. Он возвращает массив данных либо null если совпадения отсутствуют. search (en-US) Метод String, который тестирует на совпадение в строке. Он возвращает индекс совпадения, или -1 если совпадений не будет найдено. replace Метод String , который выполняет поиск совпадения в строке, и заменяет совпавшую подстроку другой подстрокой, переданной как аргумент в этот метод. split (en-US) Метод String, который использует регулярное выражение или фиксированную строку чтобы разбить строку на массив подстрок. Чтобы просто узнать есть ли в строке что либо соответствующее шаблону, воспользуйтесь методами test или search ; а чтобы получить больше информации пользуйтесь методами exec или match (хотя эти методы работают медленнее). Если вы пользуетесь exec или match и если совпадения есть, эти методы вернут массив и обновлённые свойства объекта ассоциированного регулярного выражения а также предопределённого объекта RegExp регулярного выражения. Если совпадений нет, метод exec вернёт null (который сконвертируется в false ).
В след. примере, скрипт использует метод exec чтобы найти совпадения в строке.
var myRe = /d(b+)d/g; var myArray = myRe.exec("cdbbdbsbz");
Если вам не нужен доступ к свойствам регулярного выражения, то альтернативный способ получить myArray можно так:
var myArray = /d(b+)d/g.exec("cdbbdbsbz");
Если вы хотите сконструировать регулярное выражение из строки, другой способ сделать это приведён ниже:
var myRe = new RegExp("d(b+)d", "g"); var myArray = myRe.exec("cdbbdbsbz");
С помощью этих скриптов, поиск совпадения завершается и возвращает массив и обновлённые свойства показанные в след. таблице.
Table 4.3 Результаты выполнения регулярного выражения
Объект Свойство или индекс Описание В этом примере. myArray Совпавшая строка и все запомненные подстроки. [«dbbd», «bb»] index Индекс совпавшей подстроки (индекс начинается с нуля). 1 input Исходная строка. «cdbbdbsbz» [0] Последние совпавшие символы. «dbbd» myRe lastIndex Индекс с которого начнётся след. поиск совпадения. (Это свойство определяется только если регулярное выражение использует параметр g, описанный в [Advanced Searching With Flags](#Advanced_Searching_With_Flags).) 5 source Текст шаблона. Обновляется в момент создания регулярного выражения, а не во время выполнения. «d(b+)d» Как показано во втором варианте этого примера, вы можете использовать регулярное выражение, созданное при помощи инициализатора объекта, без присваивания его переменной. Таким образом, если вы используете данную форму записи без присваивания переменной, то в процессе дальнейшего использования вы не можете получить доступ к свойствам данного регулярного выражения. Например, у вас есть следующий скрипт:
var myRe = /d(b+)d/g; var myArray = myRe.exec("cdbbdbsbz"); console.log("The value of lastIndex is " + myRe.lastIndex);
Этот скрипт выведет:
The value of lastIndex is 5
Однако, если у вас есть следующий скрипт:
var myArray = /d(b+)d/g.exec("cdbbdbsbz"); console.log("The value of lastIndex is " + /d(b+)d/g.lastIndex);
The value of lastIndex is 0
Совпадения /d(b+)d/g в двух случаях являются разными объектами регулярного выражения и, следовательно, имеют различные значения для свойства lastIndex . Если вам необходим доступ к свойствам объекта, созданного при помощи инициализатора, то вы должны сначала присвоить его переменной.
Использование скобочных выражений для нахождения подстрок
Использование скобок в шаблоне регулярного выражения повлечёт «запоминание» совпавшей подстроки. Для примера, /a(b)c/ вызовет совпадение ‘abc’ и запомнит ‘b’. Чтобы получить совпадения скобочного выражения используйте Array elements [1] , . [n] .
Число возможных скобочных подстрок неограничено. Возвращаемый массив содержит все полученные совпадения, удовлетворяющие выражению в скобках. Следующий пример показывает как использовать скобочные выражения для нахождения подстрок.
Следующий скрипт использует метод replace(), чтобы поменять местами слова (символы) в строке. Для замены текста скрипт использует обозначения $1 и $2 для обозначения первого и второго совпадения скобочного выражения.
var re = /(\w+)\s(\w+)/; var str = "John Smith"; var newstr = str.replace(re, "$2, $1"); console.log(newstr);
Выведет «Smith, John».
Расширенный поиск с флагами
Регулярные выражения имеют четыре опциональных флага, которые делают возможным глобальный и регистронезависимый поиск. Флаги могут использоваться самостоятельно или вместе в любом порядке, а также могут являться частью регулярного выражения.
Flag Description g Глобальный поиск. i Регистронезависимый поиск. m Многострочный поиск. y Выполняет поиск начиная с символа, который находится на позиции свойства lastindex текущего регулярного выражения. Чтобы использовать флаги в шаблоне регулярного выражения используйте следующий синтаксис:
var re = /pattern/flags;
var re = new RegExp("pattern", "flags");
Обратите внимание, что флаги являются неотъемлемой частью регулярного выражения. Флаги не могут быть добавлены или удалены позднее.
Для примера, re = /\w+\s/g создаёт регулярное выражение, которое ищет один или более символов, после которых следует пробел и ищет данное совпадение на протяжении всей строки.
var re = /\w+\s/g; var str = "fee fi fo fum"; var myArray = str.match(re); console.log(myArray);
Выведет [«fee «, «fi «, «fo «]. В этом примере вы бы могли заменить строку:
var re = /\w+\s/g;
var re = new RegExp("\\w+\\s", "g");
и получить тот же результат.
Флаг m используется, чтобы входная строка рассматривалась как многострочная. Если флаг m используется, то ^ и $ вызовет совпадение в начале или конце любой строки в строке ввода вместо начала или конца вводимой строки целиком.
Примеры
След. примеры показывают использование регулярных выражений.
Изменение порядка в Исходной Строке
След. пример иллюстрирует формирование регулярного выражения и использование string.split() и string.replace() . Он очищает неправильно сформатированную исходную строку, которая содержит имена в неправильном порядке (имя идёт первым) разделённые пробелами, табуляцией и одной точкой с запятой. В конце, изменяется порядок следования имён (фамилия станет первой) и сортируется список.
// The name string contains multiple spaces and tabs, // and may have multiple spaces between first and last names. var names = "Harry Trump ;Fred Barney; Helen Rigby ; Bill Abel ; Chris Hand "; var output = ["---------- Original String\n", names + "\n"]; // Prepare two regular expression patterns and array storage. // Split the string into array elements. // pattern: possible white space then semicolon then possible white space var pattern = /\s*;\s*/; // Break the string into pieces separated by the pattern above and // store the pieces in an array called nameList var nameList = names.split(pattern); // new pattern: one or more characters then spaces then characters. // Use parentheses to "memorize" portions of the pattern. // The memorized portions are referred to later. pattern = /(\w+)\s+(\w+)/; // New array for holding names being processed. var bySurnameList = []; // Display the name array and populate the new array // with comma-separated names, last first. // // The replace method removes anything matching the pattern // and replaces it with the memorized string—second memorized portion // followed by comma space followed by first memorized portion. // // The variables $1 and $2 refer to the portions // memorized while matching the pattern. output.push("---------- After Split by Regular Expression"); var i, len; for (i = 0, len = nameList.length; i len; i++) output.push(nameList[i]); bySurnameList[i] = nameList[i].replace(pattern, "$2, $1"); > // Display the new array. output.push("---------- Names Reversed"); for (i = 0, len = bySurnameList.length; i len; i++) output.push(bySurnameList[i]); > // Sort by last name, then display the sorted array. bySurnameList.sort(); output.push("---------- Sorted"); for (i = 0, len = bySurnameList.length; i len; i++) output.push(bySurnameList[i]); > output.push("---------- End"); console.log(output.join("\n"));
Использование спецсимволов для проверки входных данных
В след. примере, ожидается что пользователь введёт телефонный номер и требуется проверить правильность символов набранных пользователем. Когда пользователь нажмёт кнопку «Check», скрипт проверит правильность введённого номера. Если номер правильный (совпадает с символами определёнными в регулярном выражении), то скрипт покажет сообщение благодарности для пользователя и подтвердит номер. Если нет, то скрипт проинформирует пользователя, что телефонный номер неправильный.
Внутри незахватывающих скобок (?: , регуляное выражение ищет три цифры \d ИЛИ | открывающую скобку \( , затем три цифры \d , затем закрывающую скобку \) , (закрывающая незахватывающая скобка ) ), затем тире, слеш, или десятичная точка, и когда это выражение найдено, запоминает символ ([-\/\.]) , следующие за ним и запомненные три цифры \d , следующее соответствие тире, слеша или десятичной точки \1 , и следующие четыре цифры \d .
Регулярное выражение ищет сначала 0 или одну открывающую скобку \(? , затем три цифры \d , затем 0 или одну закрывающую скобку \)? , потом одно тире, слеш или точка и когда найдёт это, запомнит символ ([-\/\.]) , след. три цифры \d , followed by the remembered match of a dash, forward slash, or decimal point \1 , followed by four digits \d .
Событие «Изменить» активируется, когда пользователь подтвердит ввод значения регулярного выражения, нажав клавишу «Enter».
doctype html> html> head> meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" /> meta http-equiv="Content-Script-Type" content="text/javascript" /> script type="text/javascript"> var re = /\(?\d \)?([-\/\.])\d \1\d /; function testInfo(phoneInput) var OK = re.exec(phoneInput.value); if (!OK) window.alert(RegExp.input + " isn't a phone number with area code!"); else window.alert("Thanks, your phone number is " + OK[0]); > script> head> body> p> Enter your phone number (with area code) and then click "Check". br />The expected format is like ###-###-####. p> form action="#"> input id="phone" />button onclick="testInfo(document.getElementById('phone'));"> Check button> form> body> html>
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on 7 авг. 2023 г. by MDN contributors.