Pesquise e substitua no Vim em todos os arquivos do projeto

117

Estou procurando a melhor maneira de pesquisar e substituir (com confirmação) em todos os arquivos de projeto no Vim. Por "arquivos de projeto", quero dizer arquivos no diretório atual, alguns dos quais não precisam ser abertos.

Uma maneira de fazer isso pode ser simplesmente abrir todos os arquivos no diretório atual:

:args ./**

e, em seguida, faça a pesquisa e substitua em todos os arquivos abertos:

:argdo %s/Search/Replace/gce

No entanto, quando faço isso, o uso de memória do Vim salta de algumas dezenas de MB para mais de 2 GB, o que não funciona para mim.

Também tenho o plug- in EasyGrep instalado, mas quase nunca funciona - ou não encontra todas as ocorrências ou apenas trava até que eu pressione CtrlC. Até agora, minha forma preferida de realizar esta tarefa é ack-grep para o termo de pesquisa, usando sua janela de correção rápida, para abrir qualquer arquivo que contenha o termo e não tenha sido aberto antes, e finalmente :bufdo %s/Search/Replace/gce.

Estou procurando um plug-in bom e funcional que possa ser usado para isso ou, alternativamente, um comando / sequência de comandos que seria mais fácil do que o que estou usando agora.

psyho
fonte
1
@Cascabel Desde que você escreveu este comentário, existe um site vi.stackexchange.com.
Flimm

Respostas:

27

Greplace funciona bem para mim.

Há também uma versão pronta para patógenos no github.

tuxcanfly
fonte
8
Instalado, vejo os outros comandos curtir :Gsearche :Gbuffersearchexistir, mas quando digito :Greplaceentendo Not an editor command: Greplace.
Ramon Tayag
de acordo com a documentação, a sintaxe é: :Gsearch [<grep-option(s)>] [[<pattern>] [<filename(s)>]] então você poderia fazer uma pesquisa recursiva usando, por exemplo::Gsearch -r pattern dir/
vitorbal
O que eu odeio no Greplace é que, se o padrão está no nome do arquivo, o Greplace falha, porque você acaba substituindo-o no nome do arquivo também.
Daniel
2
Nada incomum aqui após 6 anos, mas este plugin está seriamente desatualizado em comparação com algumas novas respostas ( github.com/yegappan/greplace ) última atualização, mais de 3 anos atrás. Posso verificar a solução integrada de Sid: stackoverflow.com/a/38004355/2224331 (Não sou eu em outra conta, apenas uma coincidência: P)
SidOfc
99

A outra grande opção aqui é simplesmente não usar o vim:

sed -i 's/pattern/replacement/' <files>

ou se você tem alguma forma de gerar uma lista de arquivos, talvez algo assim:

find . -name *.cpp | xargs sed -i 's/pattern/replacement/'
grep -rl 'pattern1' | xargs sed -i 's/pattern2/replacement/'

e assim por diante!

Cascabel
fonte
Obrigado! Eu realmente preciso aprender essas coisas de linha de comando da velha escola. Acho que essa é a melhor resposta. Estas regexes são regexes perl ou regexes vim ou algum outro tipo de regex?
Eric Johnson
2
@EricJohnson: Eles são ... sedregexes, que são essencialmente expressões regulares básicas POSIX.2, mas não exatamente (isso está na página de manual) - eles permitem a \ncorrespondência de novas linhas e algumas outras coisas semelhantes. Eles são, portanto, praticamente iguais às grepexpressões regulares.
Cascabel
Existe uma razão para você ter -i no comando sed? A página do manual diz que é para especificar uma extensão, mas você não parece realmente especificar uma.
davekaro
Ok, na verdade parece que 's / pattern / replacement /' é a extensão, acho que entendi agora.
davekaro
11
No Mac OS X, o único comando sed que funcionou para mim foi:find . -type f | LANG=C xargs sed -i '' 's/SEARCH/REPLACE/g'
ghodss
74

EDIT: Use cfdocommand em vez de cdopara reduzir significativamente a quantidade de comandos que serão executados para fazer isso (porque cdoexecuta comandos em cada elemento enquanto cfdoexecuta comandos em cada arquivo)

Graças ao cdocomando adicionado recentemente , agora você pode fazer isso em dois comandos simples, usando qualquer grepferramenta instalada. Não são necessários plug-ins extras !:

1. :grep <search term>
2. :cdo %s/<search term>/<replace term>/gc
3. (If you want to save the changes in all files) :cdo update

( cdoexecuta o comando fornecido para cada termo na lista de correções rápidas, que o seu grepcomando preenche.)

(Remova cno final do segundo comando se quiser substituir cada termo de pesquisa sem confirmar a cada vez)

Sid
fonte
12
Além disso, você pode adicionar :cdo updatepara salvar as alterações em todos os arquivos.
Zhe Li
1
Mas fará uma pausa para avisar que não foi salvo a cada vez antes de alternar para o próximo buffer quando o buffer atual for modificado.
Livre
1
@Zhe obrigado, acrescentei isso à resposta; @lfree Eu pessoalmente prefiro confirmar cada alteração, mas você pode remover o cno final do segundo comando (apenas use g) para que ele procure e substitua sem pedir confirmação
Sid
1
: cdo% s / termo de pesquisa / substituir termo / g apenas substitui a primeira ocorrência, mas não funciona para a segunda ocorrência em meu vim
sunxd
3
para usar update, eu tinha que:cdo %s/<search term>/<replace term>/g | update
gabe
34

Decidi usar ack e Perl para resolver esse problema a fim de aproveitar as vantagens das expressões regulares Perl completas mais poderosas em vez do subconjunto GNU.

ack -l 'pattern' | xargs perl -pi -E 's/pattern/replacement/g'

Explicação

ack

ack é uma ferramenta de linha de comando impressionante que é uma mistura de grep, finde expressões regulares Perl completas (e não apenas o subconjunto GNU). É escrito em Perl puro, é rápido, tem destaque de sintaxe, funciona no Windows e é mais amigável para os programadores do que as ferramentas de linha de comando tradicionais. Instale-o no Ubuntu com sudo apt-get install ack-grep.

xargs

Xargs é uma antiga ferramenta de linha de comando Unix. Ele lê itens da entrada padrão e executa o comando especificado seguido pelos itens lidos para a entrada padrão. Então, basicamente, a lista de arquivos gerados por ack está sendo anexada ao final do perl -pi -E 's/pattern/replacemnt/g'comando.

perl -pi

Perl é uma linguagem de programação. A opção -p faz com que Perl crie um loop em torno de seu programa que itera sobre argumentos de nome de arquivo. A opção -i faz com que o Perl edite o arquivo no local. Você pode modificar isso para criar backups. A opção -E faz com que Perl execute uma linha de código especificada como o programa. Em nosso caso, o programa é apenas uma substituição de regex Perl. Para obter mais informações sobre as opções de linha de comando Perl perldoc perlrun. Para obter mais informações sobre Perl, consulte http://www.perl.org/ .

Eric Johnson
fonte
Gosto desta resposta porque funciona mesmo com terminações de linha do cygwin. Observe que ele adiciona a \ r ao final das linhas modificadas, mas ao usar sed, ele substituirá cada nova linha, tornando seus diffs inutilizáveis. Perl é um pouco mais lógico, e este é um ótimo one-liner para pesquisar e substituir. Obrigado!
Andrew,
@andrew, não sei exatamente o que está acontecendo de errado no seu caso. Isso ajudaria você a usar \ R em vez de \ n em suas expressões regulares? Consulte a seção \ R em perldoc.perl.org/perlrebackslash.html#Misc . Experimente também perldoc.perl.org/perlre.html#Regular-Expressions . Perl é extremamente multiplataforma e tenho certeza de que há uma maneira de você não acabar com terminações de linha mutiladas.
Eric Johnson
Minha regex não possui novas linhas, as versões cygwin desses programas adicionam automaticamente \ r no final de cada linha modificada. Eu gostei especificamente da versão perl porque ela apenas modifica as linhas com as quais corresponde, enquanto o sed modifica todas as linhas, mesmo que não correspondam ao regex.
Andrew
E se o caminho do arquivo contiver espaços?
shengy
23

talvez faça isso:

:noautocmd vim /Search/ **/*
:set hidden
:cfirst
qa
:%s//Replace/gce
:cnf
q
1000@a
:wa

Explicação:

  • :noautocmd vim /Search/ **/*⇒ lookup ( vimé uma abreviatura de vimgrep) padrão em todos os arquivos em todos os subdiretórios do cwd sem acionar autocmds ( :noautocmd), para aumentar a velocidade.
  • :set hidden ⇒ permite que buffers modificados não sejam exibidos em uma janela (pode estar em seu vimrc)
  • :cfirst ⇒ pular para o primeiro resultado da pesquisa
  • qa ⇒ iniciar a gravação de uma macro no registrador a
  • :%s//Replace/gce⇒ substituir todas as ocorrências do último padrão de pesquisa (ainda /Search/naquele momento) por Replace:
    • várias vezes na mesma linha ( gbandeira)
    • com confirmação do usuário ( cbandeira)
    • sem erro se nenhum padrão for encontrado ( eflag)
  • :cnf⇒ pula para o próximo arquivo na lista criada pelo vimcomando
  • q ⇒ macro para parar a gravação
  • 1000@a ⇒ reproduz macro armazenada no registrador 1000 vezes
  • :wa ⇒ salvar todos os buffers modificados

* EDITAR * Vim 8 vias:

A partir do Vim 8, há uma maneira melhor de fazer isso, pois :cfdoitera em todos os arquivos na lista de correções rápidas:

:noautocmd vim /Search/ **/*
:set hidden
:cfdo %s//Replace/gce
:wa
Benoit
fonte
você pode querer explicar isso um pouco mais para nós, mortais inferiores. Não está claro para mim se você precisa fazer essa "sequência" todas as vezes. Estou fazendo isso mais rápido usando meu velho Delphi 5 enferrujado, então certamente devo estar perdendo alguma coisa.
Lieven Keersmaekers
1
@Lieven: Você está certo, isso é um pouco obscuro sem comentários. Vou desenvolver alguma explicação.
Benoit
1, mas embora vimtenha me conquistado para muitas coisas, acho que vou ficar com o PowerGrep com isso.
Lieven Keersmaekers
Obrigado pela sua resposta e explicação de todos os comandos, agradeço muito. Estou aceitando a outra resposta porque prefiro usar um plugin para isso.
psyho
Eu marquei isso porque causa mais confusão, dada a disparidade entre o nível de alguém que faz a pergunta e o nível necessário para entender essa resposta.
Lloyd Moore,
22

Preencher a :argspartir de um comando shell

É possível (em alguns sistemas operacionais 1 )) fornecer os arquivos por :argsmeio de um comando shell.

Por exemplo, se você tiver o ACK 2 instalado,

:args `ack -l pattern`

irá pedir ao ack para retornar uma lista de arquivos contendo 'padrão' e colocá-los na lista de argumentos.

Ou com o grep simples, acho que seria:

:args `grep -lr pattern .`  


Você pode então apenas usar :argdoconforme descrito pelo OP:

:argdo %s/pattern/replacement/gce


Preencher a :argspartir da lista de correção rápida

Verifique também a resposta de nelstrom a uma pergunta relacionada que descreve um comando simples definido pelo usuário que preenche o arglist da lista de correções rápidas atual. Isso funciona muito bem com muitos comandos e plug-ins cuja saída acaba na lista de correções rápidas ( :vimgrep, :Ack3 , :Ggrep4 ).

A sequência para realizar uma pesquisa ampla do projeto poderia então ser feita com:

:vimgrep /pattern/ **/*
:Qargs 
:argdo %s/findme/replacement/gc

onde :Qargsé a chamada para o comando definido pelo usuário que preenche o arglist da lista de correções rápidas.

Você também encontrará links na discussão a seguir para plug-ins simples que reduzem esse fluxo de trabalho a 2 ou 3 comandos.

Links

  1. : h {arglist} - vimdoc.sourceforge.net/htmldoc/editing.html#{arglist}
  2. ack - betterthangrep.com/
  3. ack.vim - github.com/mileszs/ack.vim
  4. fugitivo - github.com/tpope/vim-fugitive
extraordinário
fonte
1
argdo para após o primeiro arquivo com um problema de gravação
Frankster
9

Mais uma opção em 2016, o plugin far.vim :

far.vom

Oleg Khalidov
fonte
4
Você também deve dizer que é o autor do plugin :)
sitilge
5

Se você não se importa em introduzir dependência externa, desenvolvi um plugin ctrlsf.vim (depende de ack ou ag ) para fazer o trabalho.

Ele pode formatar e exibir o resultado da pesquisa de ack / ag e sincronizar suas alterações no buffer de resultados com os arquivos reais no disco.

Talvez a demonstração a seguir explique mais

ctrlsf_edit_demo

Dyng
fonte
3
1. :grep <search term> (or whatever you use to populate the quickfix window)
2. :cfdo %s/<search term>/<replace term>/g | update

A etapa 1 preenche a lista de correção rápida com os itens que você deseja. Nesse caso, ele é propagado com os termos de pesquisa pelos quais deseja alterar grep.

cfdoexecuta o seguinte comando em cada arquivo na lista de correções rápidas. Digite :help cfdopara obter detalhes.

s/<search term>/<replace term>/gsubstitui cada termo. /gsignifica substituir todas as ocorrências no arquivo.

| update salva o arquivo após cada substituição.

Eu juntei tudo com base nesta resposta e seus comentários, mas senti que merecia sua própria resposta, pois está tudo em um só lugar.

Kyle Heironimus
fonte
Para mim no NeoVim, uma pequena modificação precisava ser feita na linha 2 acima: 2. :cfdo %s/<search term>/<replace term>/g | updateSem o %, apenas a primeira ocorrência do padrão é substituída.
Kareem Ergawy
Obrigado Kareem. Eu atualizei a resposta, já que %sfunciona no vim regular também.
Kyle Heironimus
0

Basicamente, eu queria substituir em um único comando e, mais importante, dentro do próprio vim

Com base na resposta de @Jefromi, criei um atalho de teclado, que defini no meu arquivo .vimrc como este

nmap <leader>r :!grep -r -l  * \| xargs sed -i -e 's///g'

agora do vim, com um golpe de leader + r, pego o comando carregado no vim, que edito como a seguir,

:!grep -r -l <find> <file pattern> | xargs sed -i -e 's/<find>/<replace>/g'

Portanto, eu faço a substituição com um único comando e, mais importante, dentro do próprio vim

Deepak
fonte
e eu uso ag em vez de grep
deepak