Замена :substitute и регулярные выражения Vim

Рейтинг: 52.3% · 11 голосов
Исчерпывающий курс по Vim и Neovim: модальное редактирование и грамматика (операторы, движения, текстовые объекты, точка-команда), регистры, метки, макросы, поиск и :substitute, :global, буферы/окна/quickfix, визуальный режим, конфигурация на Lua, маппинги, плагины (lazy.nvim), LSP, автодополнение (blink.cmp), Treesitter, fuzzy-поиск, Git, эргономика и антипаттерны. 34 главы, актуально на 2026 (Vim 9.1, Neovim 0.11/0.12).
Ответить
Аватара пользователя
Roman_Vim
Сообщения: 34
Зарегистрирован: 11 май 2026, 05:31

Замена :substitute и регулярные выражения Vim

Сообщение Roman_Vim »

Оглавление курса (34)
  1. Философия Vim: модальное редактирование и почему это актуально в 2026
  2. Установка и выживание: первый запуск, режимы, как выйти
  3. Перемещение по тексту: движения как основа эффективности
  4. Базовое редактирование: вставка, удаление, копирование
  5. Грамматика Vim: операторы + движения = команды
  6. Текстовые объекты: оперируем структурой, а не символами
  7. Точка-команда и повторяемость: "один штрих, чтобы повторить"
  8. Регистры: продвинутая работа с буфером обмена
  9. Метки и прыжки: навигация по истории перемещений
  10. Поиск: /, ?, *, # и магия паттернов
  11. Замена :substitute и регулярные выражения Vim (вы здесь)
  12. Команда :global - оружие массового редактирования
  13. Буферы, окна и вкладки: организация рабочего пространства
  14. Quickfix и location list: пакетная работа по всему проекту
  15. Навигация по файлам: netrw, fuzzy-поиск и проекты
  16. Макросы: автоматизация повторяющихся правок
  17. Визуальный режим и блочное редактирование
  18. Трюки режима вставки
  19. Свёртки, автодополнение и аббревиатуры (встроенные)
  20. Конфигурация с нуля: init.lua и разумные дефолты 2026
  21. Маппинги: leader, which-key и здравые привычки
  22. Скриптинг: основы Vimscript и Lua для Neovim
  23. Менеджеры плагинов: lazy.nvim и lazy-loading
  24. Neovim в 2026: чем отличается и почему выбирают
  25. LSP в Neovim: умные возможности языка
  26. Автодополнение 2026: blink.cmp против nvim-cmp
  27. Treesitter: точная подсветка и структурное редактирование
  28. Fuzzy-поиск: Telescope, fzf-lua и snacks.picker
  29. Git внутри редактора: fugitive, gitsigns и lazygit
  30. Дистрибутивы, отладка и терминал: быстрый старт и интеграции
  31. Vim повсюду: IDE, браузер и shell
  32. Эргономика и антипаттерны: как не навредить себе и не застрять
  33. Рабочие рецепты, тренажёры и план обучения
  34. Шпаргалка-справочник: все ключевые команды одним списком
Команда :substitute (сокращённо :s) - это рабочая лошадка любого редактирования в Vim: переименовать переменную, поменять местами поля, привести регистр, превратить CSV в SQL. Если поиск из главы 10 отвечает на вопрос "где?", то :substitute отвечает на вопрос "заменить это на то - и сразу везде, где нужно". А поскольку она использует тот же движок регулярных выражений, что и /, освоив замену, вы автоматически прокачиваете и поиск.

Главная причина учить :s всерьёз, а не запоминать пару заклинаний: это декларативный инструмент. Вы один раз описываете что и где изменить, и Vim применяет правило к сотням строк за миллисекунды - без макросов, без ручного перебора. А диалект регулярных выражений Vim, при всей своей исторической причудливости, умеет вещи, которых нет в обычном PCRE: точечные границы совпадения через \zs/\ze, вычисляемые замены через \=, преобразование регистра прямо в строке замены.

В этой главе мы разберём синтаксис от и до, а в главе 12 поднимемся на уровень выше - к :global, которая использует :s как одну из своих "боеголовок".

Анатомия команды

Общий синтаксис выглядит так:

Код: Выделить всё

:[range]s/{pattern}/{string}/[flags] [count]
  • [range] - на каких строках работать (по умолчанию - только текущая строка);
  • {pattern} - что искать (регулярное выражение в диалекте Vim);
  • {string} - на что заменять (с поддержкой ссылок на группы и спецзамен);
  • [flags] - модификаторы поведения (g, c, i, ...);
  • [count] - сколько строк обработать, начиная с последней строки диапазона.
Разделитель / не священен. Если в паттерне много слешей (например, при работе с путями), возьмите любой не-алфавитный символ - Vim примет первый символ после s за разделитель:

Код: Выделить всё

:s#/usr/local#/opt#      " удобно для путей
:s,old,new,g             " запятая тоже годится
Почему по умолчанию только одна замена в строке

Критичный момент для новичков: без флага g команда :s заменяет только первое совпадение в каждой строке. Это историческое наследие ed/sed, и оно постоянно подводит. Хотите заменить все вхождения в строке - добавляйте g (от global within line, не путать с командой :global).

Код: Выделить всё

Было:   foo foo foo
:s/foo/bar/
Стало:  bar foo foo

Было:   foo foo foo
:s/foo/bar/g
Стало:  bar bar bar
Диапазоны (range)

Диапазон - это то, что превращает :s из точечной правки в массовый инструмент. Адреса строк разделяются запятой.

Код: Выделить всё

Range          |  Значение
---------------+-----------------------------------------------------------------
(пусто)        |  только текущая строка
%              |  весь файл (от первой до последней строки)
1,$            |  то же, что % ($ - последняя строка)
.,$            |  от текущей строки до конца
.,+5           |  текущая строка и пять следующих
-3,.           |  три строки выше и текущая
'<,'>          |  визуальное выделение (появляется автоматически)
'a,'b          |  от метки a до метки b (о метках - [глава 9](09-marks-jumps.md))
10,20          |  строки с 10 по 20
/start/,/end/  |  от строки со start до строки с end
Самый частый практический случай: выделить блок в визуальном режиме (V, движения), нажать : - и Vim сам подставит '<,'> в командную строку. Дальше дописываете s/.../.../:

Код: Выделить всё

" выделили несколько строк через V, нажали :
:'<,'>s/TODO/DONE/g
Запомните % как рефлекс для "во всём файле" и .,+N для "здесь и ещё немного ниже". Эти два покрывают 80% задач.

Флаги

Флаги пишутся слитно после третьего разделителя и меняют поведение команды.

Код: Выделить всё

Флаг  |  Действие
------+----------------------------------------------------------------------
g     |  заменить все вхождения в строке, а не только первое
c     |  спрашивать подтверждение перед каждой заменой
i     |  игнорировать регистр в этом поиске (ignorecase локально)
I     |  учитывать регистр, даже если включён ignorecase
n     |  не заменять, а только посчитать совпадения
e     |  не выдавать ошибку, если паттерн не найден
&     |  сохранить флаги предыдущей :s (должен стоять первым в списке флагов)
Флаг & особый: он должен быть первым в списке флагов (:help :s_flags: "[&] Must be the first one"). То есть :s/a/b/&g корректно, а :s/a/b/g& - нет. Кроме того, форма :&& - это тот же приём "сохранить флаги", только записанный командой повтора (см. раздел "Повтор замены" ниже).

Флаг c - интерактивный. Vim останавливается на каждом совпадении и ждёт ответа:

Код: Выделить всё

Клавиша        |  Ответ
---------------+---------------------------------------------
y              |  заменить это
n              |  пропустить
a              |  заменить это и все оставшиеся (all)
l              |  заменить это и остановиться (last)
q              |  прекратить
<C-e> / <C-y>  |  прокрутить экран вверх/вниз, не теряя место
Флаг n бесценен для разведки. :%s/error/&/gn посчитает все вхождения error в файле и ничего не тронет (& в замене означает "само совпадение", то есть замена на самого себя - но из-за n она всё равно не происходит). По сути это счётчик: внизу появится 42 matches on 30 lines.

Код: Выделить всё

:%s/TODO//gn
" -> "17 matches on 12 lines" - файл не изменился
Магия паттернов и режим \v

Диалект регулярных выражений Vim управляется опцией magic. По умолчанию ., *, [] - спецсимволы, а вот +, ?, (), {}, | нужно экранировать обратным слешем. Это раздражает: \(\d\+\) вместо привычного (\d+).

Решение - very magic, режим \v. Поставьте \v в начало паттерна, и почти всё, кроме букв, цифр и _, становится спецсимволом - синтаксис приближается к Perl/PCRE.

Код: Выделить всё

Хочу             |  Без \v (magic)  |  С \v (very magic)
-----------------+------------------+---------------------+-----+------+--
группа           |  \(...\)         |  (...)
один или больше  |  \+              |  +
ноль или один    |  \?              |  ?
альтернатива     |  \               |                     |  \  |  ->  |
количество       |  \{2,4}          |  {2,4}
граница слова    |  \<word\>        |  <word>

Код: Выделить всё

" Поменять местами два слова. Сравните читаемость:
:%s/\(\w\+\) \(\w\+\)/\2 \1/        " magic
:%s/\v(\w+) (\w+)/\2 \1/            " very magic
Есть и обратная крайность - \V (very nomagic), где спецсимволом остаётся только \. Это режим для почти буквального текста: путей, IP-адресов, кода со скобками, который надо найти как есть.

Код: Выделить всё

" Найти буквально C:\path\to (точки и слеши не спецсимволы)
:%s/\Vorder.total/order.subtotal/g
Практика 2026: в скриптах (Vimscript/Lua) всегда фиксируйте магический режим явным \v, \m, \M или \V в начале паттерна. Иначе ваш паттерн зависит от пользовательской опции magic, и у кого-то с нестандартной настройкой код тихо сломается. Для интерактивной работы привыкайте начинать любой нетривиальный паттерн с \v.

Группы захвата и обратные ссылки

Группа \(...\) (или (...) в \v) запоминает совпавший кусок. В строке замены к нему обращаются через \1, \2, ... \9 - по порядку открывающих скобок. А \0 или & означают всё совпадение целиком.

Код: Выделить всё

Было:   Ivan Petrov
:s/\v(\w+) (\w+)/\2, \1/
Стало:  Petrov, Ivan

Код: Выделить всё

Было:   2026-06-23
:s/\v(\d{4})-(\d{2})-(\d{2})/\3.\2.\1/
Стало:  23.06.2026
& (или \0) удобно, когда нужно что-то добавить к найденному, не переписывая его:

Код: Выделить всё

Было:   important
:s/important/[&]/
Стало:  [important]

Было:   42
:s/\d\+/<&>/
Стало:  <42>
Если вам нужна группировка для квантификатора, но не нужен захват, используйте \%(...\) - это незахватывающая группа, она не сдвигает нумерацию \1/\2.

\zs и \ze - изобретение Vim вместо lookaround

Вот фича, которой стоит научиться отдельно, потому что в обычных regex такого нет. \zs задаёт начало совпадения, \ze - его конец. Текст до \zs и после \ze обязан совпасть с паттерном (это контекст), но в само совпадение - а значит, и в замену - не входит.

Это даёт эффект lookbehind/lookahead, но проще и быстрее. У Vim есть и настоящие ассерции (\@<= для lookbehind, \@= для lookahead), но они медленнее из-за бэктрекинга и читаются тяжело. На практике 2026 в подавляющем большинстве позиционных задач берут именно \zs/\ze.

Код: Выделить всё

" Заменить bar только когда перед ним foo (а перед foo - ничего особого)
Было:   foo bar baz
:s/foo \zsbar/BAR/
Стало:  foo BAR baz
" "foo " осталось нетронутым, потому что оно до \zs

Код: Выделить всё

" Поднять регистр названия только внутри тега, не трогая скобки
Было:   <name>cyberlake</name>
:s/<name>\zs.\{-}\ze<\/name>/\U&/
Стало:  <name>CYBERLAKE</name>
Обратите внимание на \{-} - это "ленивый" (non-greedy) квантификатор Vim, аналог *? в PCRE. Жадный .* сожрал бы текст до последнего </name>.

Вычисляемая замена через \=

Когда статической строки замены недостаточно, начните строку замены с \= - и всё, что после, Vim вычислит как Vimscript-выражение. Внутри выражения доступна функция submatch(): submatch(0) - всё совпадение, submatch(1)...submatch(9) - группы. В Neovim \= тоже вычисляет именно Vimscript; чтобы дотянуться до Lua-функции, обращайтесь к ней через мост v:lua.<func>() или luaeval('...') - напрямую писать Lua внутри \= нельзя.

Код: Выделить всё

" Удвоить каждое число в файле
Было:   x=3 y=10
:%s/\d\+/\=submatch(0)*2/g
Стало:  x=6 y=20

Код: Выделить всё

" Пронумеровать строки: добавить порядковый номер в начало.
" line('.') даёт номер текущей строки.
:%s/^/\=line('.').'. '/

Код: Выделить всё

" Прибавить 1 к каждому захваченному числу
Было:   item5 item9
:%s/\vitem(\d+)/\='item'.(submatch(1)+1)/g
Стало:  item6 item10
\= - это мостик от текстовых замен к настоящему программированию. Здесь работают арифметика, строковые функции (toupper(), printf(), substitute()), обращение к переменным. Для сложных трансформаций, недостижимых статической заменой, это основной инструмент.

Спецзамены: регистр и переносы строк

В строке замены доступны управляющие последовательности, которые меняют регистр или вставляют спецсимволы. Это чисто "выходная" магия - она не влияет на поиск, только на то, что подставляется.

Код: Выделить всё

Последовательность  |  Действие
--------------------+----------------------------------------------------------------------------------------------
\u                  |  следующий символ - в верхний регистр
\l                  |  следующий символ - в нижний регистр
\U                  |  всё дальнейшее - в ВЕРХНИЙ, пока не встретит \E
\L                  |  всё дальнейшее - в нижний, пока не встретит \E
\E                  |  завершить действие \U/\L
\r                  |  вставить перенос строки (разбить строку на две)
\n                  |  вставить NUL-байт (а не перевод строки!)
\\                  |  литеральный обратный слеш
\t                  |  табуляция
\&                  |  литеральный амперсанд (иначе & = всё совпадение; при включённом magic, то есть по умолчанию)
Коварная ловушка: в строке замены \n вставляет NUL (^@), а перевод строки даёт \r. В паттерне же \n ищет именно перевод строки. Эта асимметрия - классический источник испорченных файлов.

Код: Выделить всё

" Сделать заголовок: каждое слово с заглавной буквы
Было:   привет мир cyberlake
:s/\v<(\w)/\u\1/g
Стало:  Привет Мир Cyberlake

" Весь матч в верхний регистр
Было:   константа
:s/\w\+/\U&/
Стало:  КОНСТАНТА

" Разбить строку по запятой на несколько строк
Было:   a,b,c
:s/,/\r/g
Стало:  a
        b
        c

Код: Выделить всё

" CamelCase: убрать дефисы и поднять следующую букву
Было:   my-long-name
:s/\v-(\w)/\u\1/g
Стало:  myLongName
Многострочные паттерны: искать через границы строк

По умолчанию паттерн :s живёт внутри одной строки: . не совпадает с переводом строки, классы \s, \w и т.п. тоже им ограничены. Но иногда замена должна перешагнуть границу строк - например, схлопнуть перенос или вырезать кусок, занимающий две строки.
  • \n в паттерне ищет перевод строки (не путать с \n в замене, где это NUL - см. ловушку выше).
  • \_. - "точка, включая перевод строки": любой символ, в том числе конец строки.
  • \_s, \_w, \_[...] - любой класс с приставкой \_ дополнительно захватывает перевод строки. Так \_s - это "пробельный символ ИЛИ конец строки".

Код: Выделить всё

" Склеить строку, оканчивающуюся на запятую, со следующей
Было:   foo,
        bar
:%s/,\n/, /
Стало:  foo, bar

" Удалить перенос внутри тега, охватывающего две строки
Было:   <a
        href>
:%s/<a\_s\+href>/<a href>/
Стало:  <a href>
Важная тонкость диапазона: чтобы :s "увидела" соседнюю строку, она должна попадать в [range]. Замена ,\n на последней строке диапазона перенесёт в правку строку за диапазоном - это нормально, но об этом стоит помнить. Для сложных многострочных операций часто удобнее :g (глава 12) или J/gJ.

gdefault: ловушка, инвертирующая флаг g

Опция 'gdefault' меняет смысл флага g на противоположный: при :set gdefault команда :s/foo/bar/ заменяет все вхождения в строке, а :s/foo/bar/g - только первое. Идея в том, чтобы не дописывать g постоянно, но цена - сломанная переносимость: любой ваш :s начинает зависеть от чужой настройки, а в скрипте g означает то одно, то другое.

Практика 2026: не включайте gdefault. Это давно считается анти-паттерном (её даже убрали из некоторых дистрибутивов конфигов). Если хотите "по умолчанию global", заведите маппинг или абревиатуру, а сам флаг g оставьте однозначным. Когда читаете чужой :s и поведение g кажется "перевёрнутым" - первым делом проверьте :verbose set gdefault?.

:s всегда построчна (даже из visual-block)

Частое заблуждение: если выделить колонку в блочном визуальном режиме (<C-v>) и нажать :, кажется, что замена ограничится столбцом. Это не так. : подставляет диапазон '<,'> - а это всегда строковый диапазон от строки начала выделения до строки конца. Колонки теряются: :s отработает по всей ширине каждой из этих строк, как если бы вы выделяли их через V.

Код: Выделить всё

" Выделили <C-v> только первый столбец из трёх строк, нажали :
:'<,'>s/x/y/g    " заменит x во ВСЕЙ строке, а не только в столбце
Если правка нужна именно в пределах колонки, варианты другие: блочное c/r/I/A (нормальные операции visual-block), либо привязать паттерн к позиции через \%Vc (атом \%V ограничивает совпадение визуальным выделением) или через \%>Nc/\%<Nc (совпадать только в определённых столбцах). Но "голая" :s про колонки не знает.

Повтор замены: :s, :&, g&

После того как вы один раз написали :s/.../.../, не нужно набирать её заново. Vim хранит последний паттерн (общий с поиском /) и последнюю замену.

Код: Выделить всё

Команда     |  Действие
------------+---------------------------------------------------------------------------------------------------------------------
:s          |  повторить последнюю :s на текущей строке с теми же паттерном и заменой, но без флагов
:s {flags}  |  повторить с заново указанными флагами (например :s g); прежние флаги при этом не наследуются
:&          |  повторить последнюю :s без флагов (синоним голой :s)
:&&         |  повторить с сохранением флагов предыдущей :s (единственный "короткий" способ их унаследовать)
g&          |  повторить последнюю :s на всём файле (%) и с последним поисковым паттерном @/, сохраняя флаги - фактически :%s//~/&
& (normal)  |  то же, что :s - повтор на текущей строке без флагов
Важно про флаги при повторе. Голая :s, :& и :s {flags} флаги предыдущей подстановки не сохраняют (:help :&&, :s_flags: "Note that :s and :& don't keep the flags."). :s g - это не "повтор с теми же флагами", а повтор с единственным флагом g, заданным заново. Унаследовать прежний набор флагов можно только через ведущий флаг &: это :&& либо явная форма :s/.../.../&.
g& особенно мощна. Представьте: вы сделали :s/foo/bar/g на одной строке, проверили результат, и хотите применить ко всему файлу. Просто нажмите g&. Она ещё и подхватывает текущий поисковый паттерн (@/), а не тот, что был в момент :s - поэтому ей можно "скормить" новый паттерн через /, * или let @/=..., оставив прежнюю замену.

Код: Выделить всё

:s/old/new/g      " попробовали на одной строке
" результат устроил - применяем ко всему файлу:
g&
Ещё одна полезная мелочь: пустой паттерн в :s означает "использовать последний поисковый паттерн". То есть после поиска /функция можно сразу сделать :%s//метод/g - Vim подставит функция сам. Это связывает поиск и замену в единый рабочий поток.

Живой предпросмотр: inccommand (только Neovim)

Эксклюзивная фича Neovim, которой нет в классическом Vim, - inccommand. Она показывает результат :s прямо по мере набора команды.

Код: Выделить всё

vim.o.inccommand = 'split'    -- или 'nosplit'
  • nosplit - подсвечивает изменения инлайн, в самом буфере;
  • split - дополнительно открывает окно предпросмотра со всеми совпадениями, в том числе за пределами экрана.
<Esc> отменяет, <Enter> применяет. С inccommand нужда во флаге c резко падает: вы и так видите, что произойдёт, ещё до нажатия Enter. Если предпросмотр не успевает за redrawtime, Neovim сам его временно отключает, так что на гигантских файлах тормозов не будет. Подробнее о настройке опций - в главе 20.

Рецепты на каждый день

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

Код: Выделить всё

" Убрать пробелы в конце строк
:%s/\s\+$//

" Схлопнуть несколько пробелов в один
:%s/\s\{2,}/ /g

" Удалить пустые строки (только из пробелов - тоже)
:%s/^\s*$//   " очистит, но строки останутся; для удаления см. :g в главе 12

" Поменять местами поля CSV (первое и второе)
:%s/\v^([^,]+),([^,]+)/\2,\1/

" Обернуть каждое число в кавычки
:%s/\v<(\d+)>/'\1'/g

" snake_case -> camelCase
:%s/\v_(\w)/\u\1/g

" Заменить слово под курсором во всём файле (через *, затем пустой паттерн)
" 1) навели курсор, нажали *  2):
:%s//replacement/g

" Посчитать вхождения, ничего не меняя
:%s/pattern//gn
Для замены по всему проекту (множество файлов) одной командой :s мало - там в дело вступают :grep + quickfix + :cfdo. Этому посвящена глава 14. А для выборочного рефакторинга в одном файле без флага c есть элегантный приём * -> cgn -> . (меняем следующее совпадение и повторяем точкой), который мы разберём вместе с gn там же.

Лучшие практики 2026
  • Начинайте нетривиальные паттерны с \v. Very magic делает выражения читаемыми и переносимыми. В скриптах фиксация магического режима (\v/\m/\M/\V) обязательна - иначе паттерн зависит от чужой опции magic.
  • Предпочитайте \zs/\ze ассерциям \@<=/\@=. Они быстрее (нет дорогого бэктрекинга) и понятнее. Настоящие lookaround держите для редких случаев, где контекст сам по себе сложный.
  • Используйте inccommand=split в Neovim. Живой предпросмотр заменяет "слепую" отправку команды и снижает потребность во флаге c. Для классического Vim такой опции нет - там полагайтесь на c.
  • Связывайте поиск и замену через пустой паттерн. Найдите цель через / или *, проверьте навигацией, затем :%s//новое/g - это безопаснее, чем набирать паттерн дважды и ошибиться.
  • Считайте перед тем, как менять. :%s/pat//gn показывает масштаб операции. Особенно важно перед g& или заменой во всём файле.
  • \= для всего, что не статично. Арифметика, нумерация, printf - не пытайтесь вытащить это статической заменой, переходите на выражение.
  • Для рефакторинга в одном файле - cgn+., а не :s///c. Точечный, повторяемый, визуально контролируемый способ (детали - глава 14).
Частые ошибки
  • Забыли флаг g. Заменилось только первое вхождение в каждой строке. Самая частая жалоба новичка.
  • Перепутали \n и \r в замене. В строке замены перевод строки - это \r, а \n вставляет NUL-байт и портит файл. В паттерне всё наоборот: \n ищет перевод строки.
  • Жадный .* вместо ленивого .\{-}. :s/<.*>// на строке <a> <b> удалит всё от первого < до последнего >. Нужен \{-}: :s/<.\{-}>//g.
  • Зависимость от magic в скрипте. Паттерн без явного режима ломается у пользователя с нестандартным magic. Всегда фиксируйте режим в коде.
  • g& подхватывает текущий @/, а не паттерн из последней :s. Если вы между делом что-то искали, g& возьмёт новый поисковый паттерн. Иногда это удобно, иногда - сюрприз.
  • & в строке замены вставляет всё совпадение. Чтобы получить буквальный амперсанд, экранируйте: \&.
  • Диапазон по умолчанию - одна строка. Без % или явного range :s тронет только текущую строку, и кажется, что "не сработало".
  • Lookaround через \@<!/\@= ради позиции. Чаще всего это переусложнение - \zs/\ze решают ту же задачу проще.
  • Включённый gdefault переворачивает g. При :set gdefault :s/.../.../ меняет все вхождения, а g - наоборот, одно. Не включайте эту опцию; если поведение g странное - проверьте :verbose set gdefault?.
  • Ждать, что :s из visual-block тронет только столбец. '<,'> - всегда строковый диапазон; :s отработает по всей строке. Для колонок - блочные операции или атом \%V.
  • \n в паттерне vs в замене. В паттерне \n - перевод строки (так делают многострочные замены), в замене \n - NUL. Для переноса в замене нужен \r.
Упражнения
  1. Перестановка имён. В файле список вида Имя Фамилия по строке. Одной командой получите Фамилия, Имя. (Подсказка: \v, две группы, ссылки \1/\2.)
  2. Даты. Превратите все YYYY-MM-DD в DD.MM.YYYY. Проверьте, что не трогаются строки без дат.
  3. Удвоение чисел. Дан текст с числами. Используя \= и submatch(), умножьте каждое число на 10.
  4. Заголовочный регистр. Строку один два три cyberlake приведите к виду Один Два Три Cyberlake (каждое слово с заглавной). Затем отдельной командой сделайте всю строку ВЕРХНИМ регистром.
  5. Тег наружу, содержимое внутрь. В строке <title>привет</title> поднимите в верхний регистр только текст между тегами, не трогая сами теги. (Подсказка: \zs, \ze, \{-}, \U/\E.)
  6. Повтор и распространение. Сделайте :s/foo/bar/g на одной строке, убедитесь в результате, затем примените ко всему файлу одним нажатием. Какой клавишей? А как посчитать, сколько foo осталось бы заменить во всём файле, ничего не меняя?
Главное из главы
  • :[range]s/pat/rep/flags - описываете правило, Vim применяет его к диапазону. По умолчанию диапазон - текущая строка, замена - первое вхождение.
  • Диапазон делает команду массовой: % (весь файл), '<,'> (выделение), .,+N (отсюда и ниже).
  • Флаги: g (все вхождения), c (подтверждение), i/I (регистр), n (только посчитать), e (без ошибки).
  • \v (very magic) превращает диалект Vim в PCRE-подобный - учите паттерны с ним; \V - для почти буквального текста.
  • Группы \(...\)/(...) и ссылки \1...\9, &/\0 - всё совпадение. \zs/\ze точно режут границы совпадения - фирменная замена lookaround.
  • \= запускает вычисляемую замену с submatch(); \U/\L/\u/\l/\E правят регистр, \r вставляет перевод строки.
  • Повтор: голая :s/:&/:s {flags} флаги не сохраняют; унаследовать их можно только ведущим & - то есть :&& (или :s/.../.../&); g& - на весь файл с текущим паттерном. Пустой паттерн = последний поисковый.
  • Через границы строк ищут \n (в паттерне), \_., \_s; :s всегда построчна (visual-block даёт строковый диапазон, не колонки); 'gdefault' инвертирует g - не включать.
  • В Neovim включите inccommand для живого предпросмотра - это меняет ощущение от замены целиком.
  • Для замены по многим файлам - глава 14 (:grep, quickfix, :cfdo); :global как движок пакетных правок - глава 12.
👍4 ❤️ 🔥 😄 🤔
Аватара пользователя
golang13
Сообщения: 1
Зарегистрирован: 17 май 2026, 19:56

Re: Замена :substitute и регулярные выражения Vim

Сообщение golang13 »

о, вот про флаг n я не знал. думал чтобы посчитать вхождения надо grep дёргать, а тут :%s/error//gn и сразу 42 matches on 30 lines внизу. красота, теперь перед любой массовой заменой так проверяю масштаб
👍1 ❤️2 🔥 😄 🤔
Аватара пользователя
quantumoverflow
Сообщения: 1
Зарегистрирован: 12 май 2026, 22:31

Re: Замена :substitute и регулярные выражения Vim

Сообщение quantumoverflow »

Долго не мог понять почему :s/foo/bar/ меняет только первое слово в строке а второе и третье нет. оказалось без g оно по дефолту только первое совпадение в строке, привычка из sed походу. дописал g и всё ок
👍2 ❤️ 🔥1 😄 🤔
Аватара пользователя
kalima
Сообщения: 1
Зарегистрирован: 25 май 2026, 07:49

Re: Замена :substitute и регулярные выражения Vim

Сообщение kalima »

делюсь маппингом который сильно жизнь облегчил: повесил cnoremap чтобы при наборе :s сразу подставлялось \v. так very magic всегда включен и не надо городить \(\w\+\) - пишешь по человечески (\w+). в нвиме ещё inccommand=split поставил, видно результат до энтера, флаг c вообще перестал юзать
👍 ❤️ 🔥 😄 🤔1
Аватара пользователя
SolidityChan
Сообщения: 1
Зарегистрирован: 12 май 2026, 14:58

Re: Замена :substitute и регулярные выражения Vim

Сообщение SolidityChan »

Roman_Vim писал(а):\zs задаёт начало совпадения, \ze - его конец
вот это прям откровение. всю жизнь страдал с lookbehind в других regex, а тут foo \zsbar/BAR/ и всё, foo не трогается. и работает быстрее чем \@<= судя по урокам. спс
👍2 ❤️1 🔥 😄 🤔
Аватара пользователя
nigelbond5
Сообщения: 1
Зарегистрирован: 31 май 2026, 09:30

Re: Замена :substitute и регулярные выражения Vim

Сообщение nigelbond5 »

а у меня затык был с \r и \n в замене. хотел разбить строку по запятой через \n и получил кучу ^@ в файле, чуть не запорол. дочитал до ловушки - в замене перенос это \r а \n это NUL. в паттерне наоборот. жесть конечно эта асимметрия но теперь буду помнить
👍2 ❤️1 🔥 😄 🤔
Аватара пользователя
mmachida
Сообщения: 1
Зарегистрирован: 02 июн 2026, 07:14

Re: Замена :substitute и регулярные выражения Vim

Сообщение mmachida »

коротко - лучшая глава по сабститьюту что видел, особенно про g& чтоб раскатать замену с одной строки на весь файл
👍 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Поиск: /, ?, *, # и магия паттернов
Следующая глава →
Команда :global - оружие массового редактирования

Все главы курса «Vim и Neovim: модальное редактирование от основ до современного Neovim»

Поделиться темой: ✈ Telegram VK

Вернуться в «Vim и Neovim: модальное редактирование от основ до Neovim 2026»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость