Как я могу найти и заменить рекурсивно в каталоге в Vim?
Я узнал о заместительной команде Вима...
:%s/replaceme/replacement/gi
И вимгреп...
:vimgrep /findme/gj project/**/*.rb
Есть ли способ объединить их, чтобы сделать замену для всех файлов в каталоге?
8 ответов
Несмотря на то, что я пользователь Vim, я обычно использую find
а также sed
для такого рода вещей. Синтаксис регулярного выражения для sed
похож на Вима (они оба потомки ed
), хотя и не идентичны. С GNU sed вы также можете использовать -i
для редактирования на месте (обычно sed
испускает модифицированную версию в стандартный вывод).
Например:
find project -name '*.rb' -type f -exec sed -i -e 's/regex/replacement/g' -- {} +
Кусочек за кусочком:
project
= поиск в дереве проекта-name '*.rb'
= для вещей, имена которых соответствуют '*.rb'-type f
= и являются обычными файлами (не каталогами, каналами, символическими ссылками и т. д.)-exec sed
= бежатьsed
с этими аргументами:-i
= с правками на месте-e 's/regex/replacement/g'
= выполнить команду "заменить" (которая почти идентична команде Vim:s
, но обратите внимание на отсутствие%
- это по умолчанию вsed
)--
= конец флагов, имена файлов начинаются здесь{}
= это говоритfind
что найденные им имена должны быть помещены в командную строку sed здесь+
= это говоритfind
что-exec
действие завершено, и мы хотим сгруппировать аргументы как можно меньшеsed
вызовы, насколько это возможно (обычно более эффективно, чем запускать их один раз для каждого имени файла). Чтобы запустить его один раз для каждого файла, вы можете использовать\;
вместо+
,
Это общее решение с GNU sed
а также find
, Это может быть немного сокращено в особых случаях. Например, если вы знаете, что ваш шаблон имени не будет совпадать ни с одним каталогом, вы можете пропустить -type f
, Если вы знаете, что ни один из ваших файлов не начинается с -
Вы можете оставить --
, Вот ответ, который я отправил на другой вопрос с более подробной информацией о передаче имен файлов, найденных с find
другим командам.
args
а также argdo
должен делать то, что вам нужно, например
:args spec/javascripts/**/*.*
:argdo %s/foo/bar/g
Смотрите эту страницу.
Вы можете запустить vimgrep, затем записать макрос, который идет к каждой строке в выводе vimgrep и выполняет замену, что-то вроде этого: (не вводите # комментариев!)
:set autowrite #automatically save the file when we change buffers
:vimgrep /pattern/ ./**/files
qa #start recording macro in register a
:s/pattern/replace/g #replace on the current line
:cnext #go to next matching line
q #stop recording
10000@a #repeat the macro 10000 times, or until stopped by
#an "error" such as reaching the end of the list
:set noautowrite
Я не проверял его, поэтому может потребоваться небольшая настройка - протестируйте его на некоторых файлах, которые вы не против сначала испортить.
Преимущество этого перед sed в том, что регулярные выражения Vim более мощные, и вы можете добавить опцию c в: s, чтобы проверить каждую замену.
Изменить: я изменил свой оригинальный пост, так как это было неправильно. Я использовал:1vimgrep, чтобы найти первое совпадение в каждом файле, но я неправильно прочитал документы - он находит только одно совпадение во всех файлах.
Чтобы ответить на исходный вопрос о сочетании vimgrep и заменителя:
:vimgrep /pattern/ ./**/files # results go to quickfix list
:set autowrite # auto-save when changing buffers
:silent! cdo %s/replaceme/replacement/gic # cdo draws filenames from quickfix list
:set noautowrite
Оставьте c
из gic
если вы не хотите подтверждать каждую замену (в этом случае вы также можете использовать find
а также sed
как было предложено @LaurenceGonsalves).
Один из способов - открыть все файлы, на которых вы хотите выполнить подстановку (или любую команду), и запустить :bufdo <cmd>
, который будет работать <cmd>
на всех открытых буферах.
Есть плагин, который пытается это сделать: Vimgrep Replace.
Ой, подождите... есть другие: Global Replace, EasyGrep.
Для решения без плагинов, возможно argdo
помогло бы, если бы вы могли получить vimgrep
вывод на список аргументов (который может быть установлен с помощью args
), но я не могу выяснить детали. Я был бы счастлив, если бы кто-то взял идею и улучшил ее... так что вот она.
Основная идея первого плагина (я думаю, другие тоже) заключается в использовании vimgrep
сначала, а затем циклически проходить матчи с :cnext
и примените команду замещения в каждой строке. Функция для этого может быть достаточно маленькой, чтобы поместить ее в.vimrc. (Может быть, вы можете поднять один из источников плагинов?)
(Я полагаю, что hgimenez может решить проблему, но, возможно, это будет зависеть от количества обрабатываемых файлов... Плагины должны быть в порядке, независимо от того.)
Это работает для небольшого количества файлов.
vim `найди. Тип F `
: bufdo% s / pattern / replace /gec | Обновить
Вот моя пользовательская команда Neovim, которая выполняет либо grep, либо grep и замену. Это будет:vimgrep
и отображать результаты в окне списка быстрых исправлений, чтобы вы могли перемещаться и просматривать внесенные изменения. Вы можете применить все изменения впоследствии с помощью:wall
:
-- Grep
vim.api.nvim_create_user_command(
'Grep',
function(opts)
local usage = "Usage: :Grep <regex> <filter?>, i.e. :Grep /my_string/g **/*, or :Grep! s/my_string/my_replacement/g **/*"
if not opts.args or string.len(opts.args) < 1 then
print(usage)
return
end
local delim = string.sub(opts.args, 1, 1)
if delim == "s" then
delim = string.sub(opts.args, 2, 2)
end
local pargs = vim.split(opts.args, delim)
if #pargs < 3 then
print(usage)
return
end
local searchpatt = pargs[2]
if pargs[1] == "s" and #pargs < 4 then
print(usage)
return
end
local replpatt = nil
local flags = nil
local filter = ""
if pargs[1] == "s" then
if not opts.bang then
print(usage)
return
end
replpatt = pargs[3]
local bridge = vim.split(pargs[4], " ")
flags = bridge[1]
filter = bridge[2] or ""
for i = 5, #pargs do
filter = filter .. delim.. pargs[i]
end
else
if opts.bang then
print(usage)
return
end
local bridge = vim.split(pargs[3], " ")
flags = bridge[1]
filter = bridge[2] or ""
for i = 4, #pargs do
filter = filter .. delim .. pargs[i]
end
end
-- print("d:" .. delim .. "\ns:" .. searchpatt .. "\nr:" .. (replpatt or "nil") .. "\nm:" .. flags .. "\nf:" .. filter)
if searchpatt and flags and filter then
vim.cmd(":vimgrep /" .. searchpatt .. "/" .. flags .. "j " .. filter)
vim.cmd(":cw")
vim.cmd(":.cc")
if replpatt then
vim.cmd(":cfdo %s/" .. searchpatt .. "/" .. replpatt .. "/" .. flags .. "e")
end
end
end,
{ nargs = '*', bang = true }
)
Не следует избегать пробелов в выражении регулярного выражения. g
флаг является обязательным иs
флаг обязателен в сочетании с челкой (!
), который введет деструктивную операцию замены.
Вы можете перемещаться по списку быстрых исправлений, используя:cn
/:cp
и вы можете закрыть его с помощью:ccl
.
Эта статья помогла мне создать это: https://jdhao.github.io/2020/03/14/nvim_search_replace_multiple_file/