Регулярные выражения для простых смертных

Original author: Landon Schropp
  • Translation
Здравствуйте, уважаемые дамы и господа.

Мы активно ищем свежую литературу на тему регулярных выражений для начинающих. Причем в данном случае нас бы скорее привлекла не переводная, а исходно русскоязычная книга, которая каким-то образом затрагивала бы и регулярные выражения при обработке естественного языка. Хотим предложить вашему вниманию следующий текст — во-первых, напомнить об этой теме, во-вторых, продемонстрировать примерный уровень сложности, который нас интересует


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

Зачем нужны регулярные выражения?

Зачем вообще возиться с регулярными выражениями? Чем они могут помочь именно вам?

  • Сравнение с шаблоном: Регулярные выражения отлично помогают определять, соответствует ли строка тому или иному формату – например, телефонному номеру, адресу электронной почты или номеру кредитной карты.
  • Замена: При помощи регулярных выражений легко находить и заменять шаблоны в строке. Так, выражение text.replace(/\s+/g, " ") заменяет все пробелы в text, например, " \n\t ", одним пробелом.
  • Извлечение: При помощи регулярных выражений легко извлекать из шаблона фрагменты информации. Например, name.matches(/^(Mr|Ms|Mrs|Dr)\.?\s/i)[1] извлекает из строки обращение к человеку, например, "Mr" из "Mr. Schropp".
  • Портируемость: Почти в любом распространенном языке программирования есть своя библиотека регулярных выражений. Синтаксис в основном стандартизирован, поэтому вам не придется переучиваться регулярным выражениям при переходе на новый язык.
  • Код: Когда пишете код, можно пользоваться регулярными выражениями для поиска информации в файлах; так, в Atom для этого предусмотрен find and replace, а в командной строке — ack.
  • Четкость и лаконичность: Если вы с регулярными выражениями на «ты», то сможете выполнять весьма нетривиальные операции, написав минимальный объем кода.


Как писать регулярные выражения

Регулярные выражения проще всего изучить на примере. Допустим, вы пишете веб-страницу, на которой будет поле для ввода телефонного номера. Поскольку вы — ас веб-разработки, вам хочется дополнительно отображать на экране галочку, если телефонный номер валиден, и крестик X — если нет.

<input id="phone-number" type="text">
<label class="valid" for="phone-number"><img src="check.svg"></label>
<label class="invalid" for="phone-number"><img src="x.svg"></label>
input:not([data-validation="valid"]) ~ label.valid,
input:not([data-validation="invalid"]) ~ label.invalid {
  display: none;
}
$("input").on("input blur", function(event) {
  if (isPhoneNumber($(this).val())) {
    $(this).attr({ "data-validation": "valid" });
    return;
  }

  if (event.type == "blur") {
    $(this).attr({ "data-validation": "invalid" });
  }
  else {
    $(this).removeAttr("data-validation");
  }
});


Теперь, если человек введет или вставит в поле валидный номер, то отобразится галочка. Если пользователь уберет курсор из поля ввода, а в поле при этом останется недопустимое значение, то отобразится крестик.
Поскольку вы знаете, что телефонные номера состоят из десяти цифр, первым делом проверяете, чтобы isPhoneNumber выглядел так:

function isPhoneNumber(string) {
  return /\d\d\d\d\d\d\d\d\d\d/.test(string);
}



В этой функции между символами / содержится регулярное выражение с десятью \d', то есть, символами-цифрами. Метод test возвращает true, если регулярное выражение соответствует строке, в противном случае – false. Если выполнить isPhoneNumber("5558675309"), метод вернет true! Ура!

Однако, писать десять \d – слегка муторная работа. К счастью, то же самое можно сделать и при помощи фигурных скобок.

function isPhoneNumber(string) {
  return /\d{10}/.test(string);
}


Иногда, вводя телефонный номер, человек начинает с ведущей 1. Правда было бы неплохо, если бы ваше регулярное выражение обрабатывало и такие случаи? Это можно сделать при помощи символа?.

function isPhoneNumber(string) {
  return /1?\d{10}/.test(string);
}


Символ ? означает «ноль или единица», поэтому теперь isPhoneNumber возвращает true как для «5558675309», так и для «15558675309»!

Пока isPhoneNumberвполне хороша, но мы упускаем одну ключевую деталь: регулярные выражения сплошь и рядом могут совпадать не со строкой, а с частью строки. Оказывается, isPhoneNumber("555555555555555555") возвращает true, поскольку в этой строке десять цифр. Проблему можно решить, воспользовавшись якорями ^ и $.

function isPhoneNumber(string) {
  return /^1?\d{10}$/.test(string);
}


Грубо говоря, ^ соответствует началу строки, а $ — концу строки, поэтому теперь ваше регулярное выражение совпадет с целым телефонным номером.

Серьезный пример

Релиз страницы состоялся, она пользуется бешеным успехом, но есть существенная проблема. В США телефонный номер можно записать разными способами:

  • (234) 567-8901
  • 234-567-8901
  • 234.567.8901
  • 234/567-8901
  • 234 567 8901
  • +1 (234) 567-8901
  • 1-234-567-8901


Хотя пользователи и могут обойтись без пунктуации, им было бы гораздо проще вводить заранее отформатированный номер.

Пусть вы и могли бы написать регулярное выражение для обработки всех этих форматов, думаю, что это плохая идея. Как бы тщательно вы ни старались учесть все форматы, все равно какой-нибудь пропустите. Кроме того, в действительности вам интересны только сами данные, а не их форматирование. Итак, чем возиться со всей этой пунктуацией, не проще ли избавиться от нее?

function isPhoneNumber(string) {
  return /^1?\d{10}$/.test(string.replace(/\D/g, ""));
}


Функция replace заменяет пустой строкой символ \D, соответствующий любым символам кроме цифр. Глобальный флаг g приказывает функции заменить на регулярное выражение все совпадения, а не только первое.

Еще более серьезный пример

Ваша страница с телефонными номерами всем нравится, в офисе вы – король кулера. Однако, такие профессионалы как вы не останавливаются на достигнутом, поэтому вы хотите сделать страницу еще лучше.
North American Numbering Plan – это стандарт по составлению телефонных номеров, используемый в США, Канаде и еще 23 странах. В этой системе есть несколько простых правил:

  1. Телефонный номер ((234) 567-8901) делится на три части: региональный код (234), код АТС (567) и номер абонента (8901).
  2. В региональном коде и коде АТС первая цифра может быть любой от 2 до 9, а вторая и третья цифры – от 0 до 9.
  3. В коде АТС 1 не может быть третьей цифрой, если вторая цифра – это 1.


Ваше регулярное выражение уже соответствует первому правилу, но нарушает второе и третье. Пока давайте разберемся со вторым. Новое регулярное выражение должно выглядеть примерно так:

/^1?<AREA CODE><EXCHANGE CODE><SUBSCRIBER NUMBER>$;/


Номер абонента прост, он состоит всего из четырех цифр

/^1?<AREA CODE><EXCHANGE CODE>\d{4}$/


Региональный код немного сложнее. Нас интересует цифра от 2 до 9, за которой идут еще две цифры. Для этого можно использовать символьное множество! Символьное множество позволяет задать группу символов, из которых затем можно выбирать.

/^1?[23456789]\d\d<EXCHANGE CODE>\d{4}$/

Отлично, но мы устанем вручную вводить все символы от 2 до 9. Сделаем код еще чище при помощи символьного диапазона.

/^1?[2-9]\d\d<EXCHANGE CODE>\d{4}$/

Уже лучше! Поскольку региональный код такой же, как и код АТС, можно просто продублировать регулярное выражение, чтобы довести этот шаблон до ума.

/^1?[2-9]\d\d[2-9]\d\d\d{4}$/

А как сделать, чтобы не приходилось копировать и вставлять ту часть выражения, в которой содержится региональный код? Все упростится, если использовать группу! Чтобы сгруппировать символы, их нужно просто заключить в круглые скобки.

/^1?([2-9]\d\d){2}\d{4}$/

Итак, [2-9]\d\d содержится в группе, а {2} указывает, что эта группа должна фигурировать дважды.

Вот и все! Рассмотрим окончательный вариант функции
isPhoneNumber:

function isPhoneNumber(string) {
  return /^1?([2-9]\d\d){2}\d{4}$/.test(string.replace(/\D/g, ""));
}


Когда лучше обходиться без регулярных выражений

Регулярные выражения – отличная штука, просто не следует решать с их помощью некоторые задачи.

Не будьте слишком строги. Нет никакого смысла проявлять чрезмерную строгость, когда пишешь регулярные выражения. В случае с телефонными номерами, даже если мы учтем все правила из документа NANP, все равно невозможно определить, реален ли данный телефонный номер. Если я заделаю номер(555) 555-5555, то он совпадет с шаблоном, но ведь такого телефонного номера не существует.

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

Не используйте их с очень сложными строками. Полное регулярное выражение для работы с электронной почтой состоит из 6 318 символов. Простое и приблизительное выглядит так: /^[^@]+@[^@]+\.[^@\.]+$/. Общее правило таково: если у вас получается регулярное выражение длиннее одной строки кода, то, возможно, стоит поискать другое решение.
Издательский дом «Питер»
266.67
Company
Share post

Comments 22

    +3
    А что не так с книгой Д. Фридла?
      +1
      Я бы сказал, что она немного устарела. RE2, regex в Go, boost.regex и прочие новомодные нюансы в ней не затронуты по очевидным причинам. А в целом, имхо, неплохая книга как для начинающих, так и для продвинутых.
        –1
        У нее есть один фатальный недостаток
        0
        Отчего же не так? Классическая книга, правда, к сожалению, не наша…
          +1
          В своей компании писал курс для начинающих. Фридл — самый полноценный источник. Написано художественным, понятным языком с прекрасными примерами из жизни.

          1. Фридл не просто классика, а один из создателей регулярных выражений, поэтому история его глазами выглядит очень понятно.
          2. Зачем активно искать что-то для НАЧИНАЮЩИХ — небольшой статьи — выше крыши. А для продвинутых — Фридл.

            0

            man perlre?

          +1
          Мы активно ищем свежую литературу на тему регулярных выражений для начинающих. Причем в данном случае нас бы скорее привлекла не переводная, а исходно русскоязычная книга, которая каким-то образом затрагивала бы и регулярные выражения при обработке естественного языка.


          Ну положим заинтересовала бы, что ваше издательство может предложить?
          Чем Фридл не устраивает. Первой главы там вполне достаточно, чтобы освоить регулярки на очень простом бытовом уровне, чего многим хватит. А если нужно сложнее то просим читать дальше.
            0
            3. В коде АТС 1 не может быть третьей цифрой, если вторая цифра – это 1.
            Ваше регулярное выражение уже соответствует первому правилу, но нарушает второе и третье. Пока давайте разберемся со вторым.

            Поскольку региональный код такой же, как и код АТС, можно просто продублировать регулярное выражение, чтобы довести этот шаблон до ума.

            Про третье правило забыли :)
              +3
              И где-то здесь должна быть ссылка на статью с заблуждениями программистов о телефонных номерах.
                +1
                Гуглится с полпинка habrahabr.ru/post/279751/
              +3
              Если сейчас писать регулярку, обычно ее пишешь в онлайн-тестере. Там отлаживаешь, а потом уже копипастишь в код.

              Добавьте в статью ссылки на известнейшие онлайн-утилиты для regexp.
              https://regex101.com/
              http://www.regexplanet.com/
                –2
                Крайне не рекомендую книгу Фридла для регулярных выражений. Она плохая, плохо написанная и имеет кучу всяких проблем. Такая же плохая как и книга Кнута.

                Фактически оба уничтожили или как минимум покоробили свою индустрию — регулярные выражения заработали репутацию чего-то сложного и непонятного потому что Фридл написал ПЛОХУЮ, СЛОЖНУЮ, ОТВРАТИТЕЛЬНУЮ КНИГУ.

                Из литературы на рынке мне понравилось:
                Python Regular Expressions
                Б.Форта Регулярные выражения за 24 часа (аж 2004 года что ли)
                Неплохое введение если не ошибаюсь было в книге XSLT Дага Тидвелла
                  +1
                  А мне Фридл понравился, когда читал. Хорошая книга, ничего там сверхсложного нет. Зря Вы так.

                  А про какую из книг Дональда Кнута Вы говорите?
                  +1
                  Не забудьте включить в книгу то самое понятное объяснение о том, почему нельзя парсить HTML с помощью регулярных выражений. Можете перевести даже, хотя вряд ли получится.
                    0
                    Я бы порекомендовал курс компиляторов Alex Aiken, в свое время все расставил на свои места по регуляркам. Притом это были не заученные знания, а твердо усвоенные фундаментальные.
                      0
                      Для любителей головоломок есть такой вот сайтик regexcrossword.com. После него начал читать регексы почти на лету.
                        0
                        А почему простые смертные должны использовать ruby, а сказать это им вы не хотите?
                          0
                          > Поскольку вы знаете, что телефонные номера состоят из десяти цифр.

                          Это без международного префикса, а он тоже входит в телефонный номер. Рекомендация Международный союза электросвязи E.164 ограничивает длину телефонного номера до 15 цифр включительно. Но это лишь рекомендация. Фактически же длинна телефонного номера ничем не ограничена.
                            +1
                            Стоило бы наверно написать предупреждение о регэксах? Ну, то самое, которое «если у вас есть проблема».
                              0
                              «Символ? означает «ноль или единица»» — неужели так сложно было дописать в конце «повторений» или «раз»?
                              Если Вы пишете статью для новичков, то будьте добры быть точными в выражениях. А вот если это для тех, кто и так знает регулярные выражения — зачем им вообще читать такую статью?..
                                0
                                /Символ? означает «ноль или единица»

                                я бы сказал этот символ скорее означает возможное отсутствие единицы, но никак не ноль
                                  –1
                                  Скачал книгу Д. Фридла на посмотреть почитать
                                  В книге упоминается утилита egrep на Win её не нашел
                                  В интернете есть утилита grep не понял как с ней работать.

                                  Only users with full accounts can post comments. Log in, please.