:global - это не "ещё один поиск". Это движок пакетной обработки: "найди все строки, подходящие под паттерн, и выполни над каждой вот эту Ex-команду". Освоив её, вы будете решать за одно нажатие Enter задачи, на которые в обычном редакторе ушли бы десятки кликов или отдельный скрипт. Эта глава - про то, как :global устроена, почему она так работает, и какие рецепты делают её незаменимой.
Зачем это нужно
Представьте типичные задачи:
- Удалить из лог-файла все строки, содержащие DEBUG.
- Оставить только строки с ERROR, выбросив всё остальное.
- Собрать все строки с TODO в конец файла.
- Добавить точку с запятой в конец каждой строки, объявляющей переменную.
- Пронумеровать или закомментировать определённые строки.
- Отсортировать или перевернуть блок строк без внешних утилит.
Как устроена :global: анатомия команды
Полный синтаксис:
Код: Выделить всё
:[range]g/{pattern}/{cmd}
- [range] - диапазон строк. По умолчанию - весь буфер (%), что отличает :global от :substitute, где дефолтный диапазон - только текущая строка.
- g - сокращение от :global.
- /{pattern}/ - паттерн поиска (те же регулярные выражения, что в главе 11; разделителем может быть не только /).
- {cmd} - любая Ex-команда, которая выполнится на каждой совпавшей строке. Если команду опустить, по умолчанию выполняется :print (вывод строки).
Самое важное, что нужно усвоить: :global работает в два прохода, и понимание этого избавляет от множества загадочных багов.
- Первый проход (mark): Vim сканирует весь диапазон и помечает внутренней меткой каждую строку, совпавшую с паттерном. Никакие изменения ещё не вносятся.
- Второй проход (execute): Vim проходит по помеченным строкам и выполняет {cmd} на каждой.
:vglobal (он же :g!) - инверсияТонкость с многострочными паттернами. Если паттерн содержит \n (то есть может растягиваться на несколько строк), Vim при пометке учитывает только начало совпадения - помечается лишь та строка, где совпадение начинается. Это частый источник сюрпризов: команда выполнится один раз на стартовой строке, а не "на всём блоке". Для работы с блоками надёжнее задавать диапазон внутри :g (см. раздел "Диапазоны внутри :global"), а не закладываться на \n в самом паттерне.
Близнец :global - :vglobal (:v удобно запоминать как inVerse, хотя это лишь мнемоника, а не официальная этимология - в документации Vim :v/:vglobal определяется просто как синоним :g!), он же :g!. Он выполняет команду на всех строках, которые не совпали с паттерном:
Код: Выделить всё
:v/{pattern}/{cmd}
:g!/{pattern}/{cmd}
Таблица базовых команд
Код: Выделить всё
Команда | Что делает
-----------------------+----------------------------------------------------------------
:g/pat/ | Вывести (:print) все строки с pat
:g/pat/d | Удалить все строки с pat
:v/pat/d | Удалить все строки без pat (оставить только совпавшие)
:g!/pat/d | То же, что :v/pat/d
:g/pat/m$ | Переместить все строки с pat в конец буфера
:g/pat/m0 | Переместить все строки с pat в начало (порядок инвертируется)
:g/pat/t. | Продублировать (скопировать под себя) каждую строку с pat
:g/pat/normal! {keys} | Выполнить нормальные команды {keys} на каждой совпавшей строке
:g/pat/s/a/b/g | Запустить :substitute только на совпавших строках
:g/pat/normal! @q | Прогнать макрос из регистра q по совпавшим строкам
:g/^/m0 | Перевернуть весь буфер (реверс строк)
:[range]g/pat/cmd | Ограничить действие диапазоном вместо всего буфера
Команды над строками: удаление, перемещение, копированиеВ реальном коде у :normal используйте форму с восклицательным знаком - :g/pat/normal! {keys} - чтобы игнорировать пользовательские маппинги (подробнее ниже и в "Лучших практиках").
Внутри :global чаще всего используют четыре Ex-команды редактирования строк. Запомните их - это рабочие лошадки.
Код: Выделить всё
Ex-команда | Полное имя | Действие
---------------------+--------------+----------------------------------------
:d | :delete | Удалить строку
:m {addr} | :move | Переместить строку после адреса {addr}
:t {addr} (или :co) | :copy | Скопировать строку после адреса {addr}
:p | :print | Вывести строку (дефолт)
- $ - последняя строка буфера.
- 0 - "перед первой строкой" (виртуальная нулевая позиция).
- . - текущая строка (та, на которой сейчас выполняется команда внутри :g).
Код: Выделить всё
Было: :g/^#/d Стало:
code line 1 (удалить code line 1
# comment все строки- another line
another line комментарии)
# another comment
А инверсия оставляет только нужное:Best-practice из :help :global: удаляйте в чёрную дыру. Каждое :d без аргумента кладёт удалённую строку в безымянный регистр " (и сдвигает нумерованные регистры). При массовом :g/pat/d это бессмысленно затирает буфер обмена и чуть тормозит. Удаление в чёрную дыру _ ничего не сохраняет, не трогает ваши регистры и работает быстрее:
`
:g/pat/d _
`
Это официальный пример из документации - берите его привычкой для любых разрушительных :g, после которых вы хотите сохранить содержимое регистров (например, чтобы потом сделать "0p).
Код: Выделить всё
Было: :v/ERROR/d Стало:
INFO: starting (удалить ERROR: disk full
ERROR: disk full всё, кроме ERROR: timeout
WARN: low memory ERROR)
ERROR: timeout
INFO: done
Собрать все совпадающие строки в конец файла, сохранив их относительный порядок:
Код: Выделить всё
Было: :g/TODO/m$ Стало:
TODO fix parser impl done
impl done more code
TODO add tests TODO fix parser
more code TODO add tests
Копирование: :g/pat/t.
:t. (copy to "здесь") дублирует каждую совпавшую строку прямо под ней:
Код: Выделить всё
Было: :g/^name:/t. Стало:
name: Alice name: Alice
age: 30 name: Alice
age: 30
Сортировка и реверс через :g/^/m0
Один из самых элегантных трюков: перевернуть порядок строк во всём буфере (или диапазоне).
Код: Выделить всё
:g/^/m0
Код: Выделить всё
Было: :g/^/m0 Стало:
one four
two three
three two
four one
Нормальные команды над совпадениями: :g/pat/normalВажно: для сортировки по алфавиту не нужна :global - в Vim есть штатная команда :sort (и :sort! для обратного порядка, :sort u для удаления дублей, :sort n для числовой сортировки). :g/^/m0 - это именно реверс, а не сортировка. Не путайте.
Это самая мощная связка. :normal {keys} выполняет последовательность клавиш нормального режима так, будто вы их набрали. В сочетании с :g вы получаете "выполни эти нажатия на каждой подходящей строке".
Код: Выделить всё
:g/foo/normal A;
Код: Выделить всё
Было: :g/^let/normal A; Стало:
let x = 1 let x = 1;
print(x) print(x)
let y = 2 let y = 2;
- Esc автоматически не нужен в конце - :normal сам выходит из режима вставки после выполнения. Но если вам нужен <Esc> в середине последовательности, его придётся вставить буквально. В командной строке это делается через Ctrl-V Esc (отобразится как ^[), либо используйте :execute "normal! A;\<Esc>jj".
- Используйте :normal! (с восклицательным знаком), чтобы игнорировать пользовательские маппинги - иначе ваши кастомные ремапы клавиш могут испортить выполнение. Внутри :g это особенно важно для надёжности.
- Курсор на каждой итерации стоит в первом столбце (column 1) совпавшей строки - то есть на самом первом символе, включая ведущие пробелы/табы (а не на первой непробельной позиции). Это важно помнить рядом с column-sensitive командами вроде I/A: :g/./normal! x на строке с ведущим отступом удалит именно ведущий пробел/таб. Если нужно встать на первый непробельный символ, добавьте ^ в начало последовательности, например :g/pat/normal! ^....
Записанный макрос (глава 16) можно запустить только на нужных строках:
Код: Выделить всё
:g/pattern/normal @q
Комбинация с :substitute
Внутри :g можно вызывать :s, и это открывает интересную возможность: делать замену только в строках, удовлетворяющих одному паттерну, по другому паттерну.
Код: Выделить всё
:g/pattern_A/s/pattern_B/replacement/g
Код: Выделить всё
Было: :g/^config/s/=/: / Стало:
config host=localhost config host: localhost
data host=remote data host=remote
config port=8080 config port: 8080
Инверсия тоже работает - :v/pattern_A/s/.../.../ заменит во всех строках, кроме содержащих pattern_A.
Тонкость: :g задаёт last search pattern. По документации :help :global команда :g устанавливает не только последний паттерн поиска, но и "последний использованный паттерн подстановки". Поэтому, если паттерн для :s совпадает с паттерном :g, во вложенном :s его можно опустить - пустой паттерн // подставит паттерн от :g:
`
:g/TODO/s//DONE/g
`
Здесь s//DONE/g заменяет именно TODO (паттерн взят из :g/TODO/) на DONE. Лаконично, но именно из-за этой "магии пустого паттерна" бывает неожиданно - держите в голове, что :g перетирает last search/substitute pattern.
Мощные рецептыЗаметьте: ту же задачу иногда решает один :s с хитрым паттерном через \zs/\ze (см. главу 11), но :g + :s обычно читается яснее, когда "строка-условие" и "текст-замена" - это две разные вещи.
Соберём арсенал реальных однострочников.
Удалить все пустые строки:
Код: Выделить всё
:g/^$/d
Схлопнуть несколько подряд идущих пустых строк в одну. Сначала удалить пустые, у которых следующая тоже пустая:
Код: Выделить всё
:g/^\s*$/,/\S/-j
Пронумеровать совпавшие строки (через внешнюю команду или :normal):
Код: Выделить всё
:g/^/normal! I-
Перенести все строки import в начало файла, сохранив порядок:
Код: Выделить всё
:g/^import/m0
Удалить строки между парой маркеров (диапазон внутри :g):
Код: Выделить всё
:g/BEGIN/,/END/d
Подсчитать количество совпадений без изменений - самый безопасный "dry run":
Код: Выделить всё
:g/pattern/p
Скопировать все совпавшие строки в регистр (накопление через append-регистр):
Код: Выделить всё
:g/ERROR/y A
Условие "совпало A, но НЕ B" - вложенный :g/:v. В качестве {cmd} для :g может выступать другой :g или :v. Это документированная техника фильтрации по двум критериям сразу. Удалить строки, где есть foo, но нет bar:
Код: Выделить всё
:g/foo/v/bar/d
Диапазоны внутри :global
Внутри второго прохода :g адрес . (текущая строка) указывает на совпавшую строку. Это позволяет строить относительные диапазоны:
Код: Выделить всё
:g/pat/.,+2d " удалить совпавшую строку и две следующие
:g/pat/.+1d " удалить строку ПОСЛЕ каждого совпадения
:g/start/,/end/d " удалить блок от совпадения до ближайшего 'end'
Лучшие практики 2026
- Фиксируйте магический режим паттерна. Как и в :s, для нетривиальных паттернов начинайте с \v (very magic) - тогда (), +, ?, |, {} работают без бэкслешей: :g/\v^(foo|bar)/d. В скриптах (Vimscript/Vim9script) обязательно явно задавайте режим (\v/\m/\M/\V), чтобы команда не зависела от пользовательской опции 'magic' и не сломалась на чужой машине. Подробнее о магических режимах - глава 11.
- Всегда делайте "dry run". Перед разрушительной :g/pat/d запустите :g/pat/p или :g/pat/#, чтобы увидеть список затрагиваемых строк. Дёшево и спасает от катастроф.
- Используйте :normal!, а не :normal. Восклицательный знак игнорирует ваши маппинги - поведение становится воспроизводимым независимо от конфига. Для надёжных скриптов это правило по умолчанию.
- Ускоряйте тяжёлые :g на больших файлах. Главный тормоз :g по огромному буферу - это перерисовка экрана на каждой итерации и срабатывание автокоманд. Оберните вызов, чтобы погасить лишнюю работу: :silent подавляет вывод, :noautocmd отключает автокоманды (актуально, когда {cmd} дёргает обработчики событий), а опция 'lazyredraw' (set lazyredraw) откладывает перерисовку до конца. Например: :noautocmd silent g/pat/normal! .... На файлах в сотни тысяч строк это даёт заметный выигрыш; на коротких - разницы нет, не усложняйте.
- Знайте границу между :g и quickfix-инструментами. :global работает в пределах одного буфера. Для пакетных правок по многим файлам проекта используйте :grep/ripgrep + :cfdo (глава 14) или современный grug-far.nvim - там есть редактируемый предпросмотр результатов и синк обратно в файлы. :g - это "массовое редактирование внутри файла", quickfix - "по всему проекту".
- Для замены в одном файле с визуальным контролем рассмотрите паттерн * + cgn + . (глава 11): он позволяет выборочно применять правку к каждому следующему совпадению. :g хорош, когда правка должна примениться ко всем совпадениям без раздумий.
- :g доступна в "настоящем" Vim/Neovim, но не везде. В режимах эмуляции Vim внутри IDE (IdeaVim, VSCodeVim) :global и :normal часто не поддерживаются или урезаны. Полноценная семантика - в vscode-neovim, который встраивает реальный Neovim. Если вы живёте в IDE, не рассчитывайте на :g слепо (см. главу 31).
- Забытый диапазон. Помните: дефолт :g - весь буфер. Если хотели обработать только выделение, начните с него: :'<,'>g/pat/d. Иначе удалите по всему файлу.
- Разрушительная :g/pat/d без предпросмотра. Один неверный паттерн - и пол-файла нет. Сначала p/#, потом d. И помните про u (undo) - :g откатывается одним u целиком, это её приятное свойство.
- <Esc> в :normal не вставился буквально. Если последовательность требует выхода из вставки в середине, нельзя просто напечатать "Esc" - нужно Ctrl-V Esc (отобразится ^[) или, надёжнее, :execute "normal! ...\<Esc>...". Обычный набор Esc просто оборвёт ввод командной строки.
- Путаница m0 и сохранения порядка. :g/pat/m0 инвертирует порядок собранных строк. Если нужен исходный порядок - собирайте в конец (m$) или используйте :sort после.
- Путаница :t. и :t$. . дублирует строку на месте, $ - отправляет копию в конец файла. Перепутаете адрес - получите не то.
- Ожидание inccommand-предпросмотра. Живой предпросмотр в Neovim есть у :substitute, но не у :global. Не полагайтесь на него - делайте явный dry run.
- :normal без ! ломается на чужих маппингах. Если в конфиге переопределены A, dd и прочие, :g/pat/normal A; поведёт себя странно. Берите :normal!.
- Чистка логов. Возьмите любой лог-файл (или создайте его с строками INFO, WARN, ERROR, DEBUG). Одной командой оставьте только строки с ERROR. Затем второй командой удалите все строки DEBUG, не трогая остальные. Какую команду вы использовали для "оставить только" и почему :v удобнее, чем сложный :g?
- Сборщик TODO. В файле с кодом разбросаны строки с TODO. Соберите их все в конец файла, сохранив относительный порядок. Затем продублируйте каждую TODO-строку прямо под ней. Подсказка: m$ и t..
- Реверс и сортировка. Создайте список из 6 строк. Переверните его одной командой :g. Затем верните как было, но уже отсортировав по алфавиту (вспомните, что для сортировки :g не нужна). Объясните, почему :g/^/m0 инвертирует порядок.
- Массовое дописывание. В файле есть строки, начинающиеся с let (или var), и обычные строки. Допишите ; в конец только строк с объявлениями, используя :g + :normal. Затем сделайте то же, но через :g + :s (без :normal). Сравните читаемость.
- Контекстная замена. В конфиг-файле есть строки config key=value и data key=value. Замените = на : только в строках, начинающихся с config, не трогая data-строки. Решите это связкой :g/.../s/.../.../.
- Блочное удаление. В файле есть несколько блоков вида BEGIN ... END. Удалите все такие блоки целиком одной командой, используя диапазон внутри :g (:g/BEGIN/,/END/d). Проверьте на dry run, что зацепится именно то, что нужно.
- :[range]g/pat/cmd выполняет Ex-команду cmd на каждой строке, совпавшей с pat; дефолтный диапазон - весь буфер.
- :v/pat/cmd (он же :g!) - инверсия: команда на всех строках, кроме совпавших. :v/pat/d = "оставить только совпавшие".
- :g работает в два прохода (сначала помечает строки, потом выполняет команду) - поэтому :g/pat/d корректно удаляет даже соседние совпадения.
- Рабочие лошадки внутри :g: :d (удалить), :m{addr} (переместить), :t{addr} (копировать), :p (вывести). Адреса: $ - конец, 0 - начало, . - текущая строка.
- :g/^/m0 инвертирует порядок строк; для сортировки есть отдельная :sort.
- :g/pat/normal! {keys} прогоняет нажатия нормального режима по совпадениям; :g/pat/normal! @q - макрос по совпадениям. Используйте normal! для надёжности.
- :g/A/s/B/C/g - контекстная замена: меняем B на C только в строках с A.
- Перед :g/pat/d делайте dry run через :g/pat/p; у :g, в отличие от :s, нет inccommand-предпросмотра.
- :g - это массовое редактирование в одном буфере. Для многофайловых правок переходите к quickfix и :cfdo (глава 14).