Однажды мы в documentat.io решили спасти наших техписов от рутинной ручной замены кавычек и написали для них статью про умную автозамену — с использованием регулярных выражений. Теперь решили поделиться ей на Хабре.

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

Какую проблему решаем?

Представьте, что вам нужно заменить дефисы на длинные тире в вордовском документе или Markdown-файле на 100500 символов, в котором много блоков кода. Использовать обычную автозамену нельзя — она покосит ваш газон вместе с клумбой и забором. Перед вами выбор:

  • Делать это вручную всю ночь.

  • За 15 минут настроить умную автозамену и всё быстро поправить. Освободившуюся ночь потратить на что-нибудь полезное.

Если вы за второй вариант, самое время разобраться с регулярными выражениями.

Что такое регулярные выражения?

Регулярные выражения — это шаблоны поиска и замены фрагментов текста, доступные в большинстве современных редакторов.

Для создания шаблона используются метасимволы (символы-джокеры, wildcard characters), каждый из которых соответствует любым символам из определённой группы. Например, \S обозначает любой символ, кроме пробела, а \d — любую цифру. Джокеры можно в произвольном порядке комбинировать с обычными символами, накладывать на них разнообразные условия и группировать при помощи скобок. Это и позволяет настроить умную автозамену.

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

Инструмент обычно активируется флажком, привязанным к полям Найти и Заменить (CTRL + H) в текстовом редакторе.

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

Работоспособность всех примеров из этой статьи проверена в VS Code.

Дисклеймер: Любая автозамена может поломать в тексте конструкции, которые вы ломать не планировали. Делайте бекапы и внимательно проверяйте, что результаты поиска соответствуют вашим ожиданиям. И да поможет вам CTRL + Z!

Справочник по синтаксису

Большинство символов в регулярных выражениях представляют сами себя, за исключением специальных символов: [ ] \ / ^ $ . | ? * + ( ) { }. Чтобы представить их в качестве символов текста, установите перед ними обратную косую черту, например: \[ — левая квадратная скобка.

Далее приведены таблицы с символами-джокерами и символами операций, которые используются для построения регулярных выражений.

Символы-джокеры

Символ-джокер

Чему соответствует

Пример

\d

Любая цифра от 0 до 9

\d\d7
Найдёт все трёхзначные последовательности цифр, оканчивающиеся на 7.
В числе 7777777 найдёт две последовательности (с 1 по 3 символы и с 4 по 6)

\D

Любой символ, кроме цифры

\D-747
Найдёт g-747 в Boeing-747 и р-747 в Бройлер-747, но не найдёт 8-747

\s

Пробел или знак табуляции

\s-\s
Найдёт каждый окружённый пробелами дефис

\S

Любой символ, кроме пробельного

—\S
Найдёт все тире, за которым не следует пробел

\w

Латинская буква, цифра или знак подчёркивания

\wодержание
Найдёт все слова cодержание, где затесалась латинская c

\W

Любой символ, кроме латинской буквы, цифры или знака подчёркивания

\W\.txt
Найдёт в тексте имена файлов с расширением .txt, которые заканчиваются на любой символ, кроме латинской буквы, цифры или знака подчёркивания

.

Любой символ

с.н
Найдёт все трёхбуквенные последовательности, начинающиеся на с и заканчивающиеся на н: сон, сын, сан и т. п., даже если они встречаются внутри слова: пасынок

\b

Граница слова (работает только с латиницей)

\bdoc\b
Найдёт все вхождения слова doc, но проигнорирует такие слова, как document или redoc

^

Начало строки

^if
Найдёт все вхождения if, стоящие в начале строки

$

Конец строки

\s$
Найдёт пробелы, стоящие в конце строк

Символы условий, диапазонов и группировки

Символ

Значение

Пример

*

Предыдущий символ повторяется любое количество раз (включая 0)

<p>.*</p>
Найдёт теги <p> и </p> и весь текст между ними. Если текста нет, найдёт только теги: <p></p>

+

Предыдущий символ повторяется 1 и более раз

<p>.+</p>
Найдёт теги <p>, </p> и весь текст между ними, но не найдёт теги без текста: <p></p>

?

Предыдущий символ повторяется 0 или 1 раз (но не более)

8?-?923 
Найдёт комбинации 923, 8923, 8-923

{2}

Предыдущий символ повторяется ровно указанное число раз

\s\d{8}\s
Найдёт все 8-значные числа, окружённые пробелами

{2,}

Предыдущий символ повторяется указанное число раз или больше

\s{2,}
Найдёт все множественные пробелы

|

Логическое ИЛИ

кот|кош
Найдёт все слова с  кот и кош (коты, кошки, антрекот, кошмар). Не найдёт коротышку

[ ]

Любой из перечисленных символов, диапазон

[a-zA-Z]
Найдёт все символы латинского алфавита в нижнем или верхнем регистре. 

[а-дэ-я]
Найдёт все символы в диапазоне с а до д и с э по я.

[аиб]
Найдёт все перечисленные символы (не комбинацию)

[^ ]

Логическое НЕ. Всё, что не соответствует указанному символу или не попадает в диапазон

[^a-z]
Найдёт все символы, кроме символов латинского алфавита

( )

Группировка символов. Символы условий применяются ко всей группе, за которой стоят

([a-z]|\s)+
Во фразе Май  инглиш is bad найдёт словосочетание is bad и пробелы.

Для сравнения без скобок:

[a-z]|\s+
Найдёт буквы i,s,b,a,d, два одиночных пробела и один двойной

\

Экранирование служебных символов

.+\.\)
Найдёт любую комбинацию символов, заканчивающуюся точкой с закрывающей скобкой

\2

Подстановка группы с указанным номером. Группы объявляются круглыми скобками. Номер группы — это порядковый номер открывающей скобки

(.+)-\1 и (.+)-\2

Найдёт фразы типа наф-наф и нуф-нуф, пыщ-пыщ и сну-сну

Специальные символы для поля замены

Символ

Назначение

Пример

$2 или \2

Подстановка группы с указанным номером.

Синтаксис может быть таким же, как в  поле поиска (\2), но может и отличаться ($2). Проверьте экспериментально, какой вариант используется в вашем редакторе

Поиск: (Иван) (Иванович) (Иванов)
Замена: $3 $1 $2
Результат: Иванов Иван Иванович

\u

Переводит первую букву следующей группы в верхний регистр

Поиск: (reg)(exp)
Замена: \u$1\u$2
Результат: RegExp

\U

Переводит все буквы следующей группы в верхний регистр

Поиск: (Unix)
Замена: \U$1
Результат: UNIX

\L

Переводит все буквы следующей группы в нижний регистр

Поиск: (IPhone)
Замена: \L$1
Результат: iphone

\l

Переводит первую букву следующей группы в нижний регистр

Поиск: (IPhone)
Замена: \l$1
Результат: iPhone

Примеры регулярных выражений

В поле поиска

В поле замены

Что делает?

([^^\s])(\s{2,})

$1   

Заменяет множественные пробелы на один, не трогая выравнивающие пробелы, которые стоят в начале строки. Позволяет не поломать отступы в примерах кода. Первая ^ является частью конструкции [^ ], а вторая означает начало строки

(\S\s)(-)(\s\S)

$1—$3

Заменяет дефисы-вместо-тире на тире. Не затронет нормальные дефисы и комбинации типа rm -rf

\s+$

Поле замены оставьте пустым

Удаляет пробелы в конце строк

"([^"]+)"

«$1»

Заменяет " " на « » во всём тексте. Используйте осторожно, если в документе есть примеры кода

(^\s*)(\*|\+)(\s)

$1-$3

Заменяет маркировку списков в Markdown со * и + на -. Работает для списков любой вложенности

Как подобрать регулярное выражение самому?

Лучший способ научиться составлять регулярные выражения — потренироваться.

Мы подготовили для вас ужасающий пример текста. Скопируйте его в пустой текстовый файл в вашем любимом текстовом редакторе.

Как здесь автоматически заменить " " на « » в названиях компаний, не затронув примеры кода?

  1. Выделите в искомых фрагментах характерные особенности, которые будут основой шаблона поиска.
    Вам повезло — все компании названы по-русски. Значит, в качестве основы шаблона можно взять кириллические символы в кавычках. Нажмите CTRL + H, включите в поле поиска регулярные выражения, наберите: "[а-я]+" и нажмите Найти.

  2. Проанализируйте результат поиска. Попадают ли в заданный шаблон лишние элементы или каких-то элементов не хватает? Корректируйте шаблон поиска, пока не убедитесь, что он в полной мере решает свою задачу.
    Поиск проигнорировал словосочетания, поскольку в шаблоне не указано, что кроме букв в кавычках могут встречаться пробелы. Исправьте это:  "([а-я]|\s)+". Скобки здесь необходимы для группировки, чтобы + относился ко всей конструкции, а не только к \s . Теперь в результаты поиска попадают все названия компаний.

  3. Определите, какие части найденного текста должны остаться без изменений. Сгруппируйте соответствующие им элементы в поле поиска с помощью скобок. В поле замены вы сможете ссылаться на эти группы по номеру. Номер группы — это порядковый номер открывающей скобки.
    Без изменений должны остаться все символы в кавычках, поместите их в скобки:
    "(([а-я]|\s)+)"

  4. Подберите шаблон замены, используя подготовленные группы.
    В поле замены сошлитесь на группу из поля поиска. Нам нужно всё содержимое кавычек, а не отдельные символы, то есть группа с номером 1.  Поставьте вокруг нужные кавычки: «$1» (или «\1»). 

  5. Нажмите Заменить всё и наслаждайтесь результатом!

Попробуйте на том же примере выполнить ещё несколько упражнений, не меняя ничего вручную, используя только регулярные выражения:

  • Приведите аббревиатуры ООО, ЗАО и ОАО к должному виду.

    Поиск: ( ооо | зао | оао )
    Замена: \U$1

  • Переставьте инициалы, чтобы они стояли после фамилий. Поставьте между инициалами пробелы.

    Поиск: ([а-я]\.)\s?([а-я]\.)\s?([а-я]+)
    Замена: $3 $1 $2

  • Приведите все номера телефонов к международному формату: +7-000-000-0000

    Поиск: 8?-?(\d{3})-?(\d{3})-?(\d{4})
    Замена: +7-$1-$2-$3

Авторы: Костя Макушев, Ната Мелихова
Редактор: Ната Мелихова

Комментарии (3)


  1. vovasik
    18.12.2023 15:36

    очень понравилось, жду два отдельных поста на хабре про if и else. Спасибо большое


    1. makushevkm Автор
      18.12.2023 15:36

      Товарищи синьор-разрабы, проходите мимо, не задерживайте очередь, это не для вас написано :)


  1. init0
    18.12.2023 15:36

    <p>.*</p>Найдёт теги <p> и </p> и весь текст между ними. Если текста нет, найдёт только теги: <p></p>

    Ошибочный пример т.к. эта регулярка в коде

    <div><p>Test</p><p>Test</p></div>

    найдет <p>Test</p><p>Test</p> - т.к. по дефолту квантор "жадный", вот так будет правильно: <p>.*?</p>

    А вообще регулярки для поиска/парсинга в HTML моветон