Como fazer uma substituição de texto em uma hierarquia de pastas grande?

9

Eu quero pesquisar e substituir algum texto em um grande conjunto de arquivos, excluindo algumas instâncias. Para cada linha, quero um prompt perguntando se preciso substituir essa linha ou não. Algo semelhante ao :%s/from/to/gc do vim (com c para solicitar confirmação), mas em um conjunto de pastas. Existe alguma ferramenta ou script de linha de comando que possa ser usado?

    
por balki 03.05.2011 / 14:49

3 respostas

17

Por que não usar o vim?

Abra todos os arquivos no vim

vim $(find . -type f)

Ou abra apenas arquivos relevantes (conforme sugerido pelo Caleb)

vim $(grep 'from' . -Rl)

E, em seguida, execute a substituição em todos os buffers

:bufdo %s/from/to/gc | update

Você também pode fazer isso com sed , mas meu conhecimento sed é limitado.

    
por 03.05.2011 / 15:41
5

Você pode fazer algo cru com um pequeno script Perl que é instruído a realizar substituições linha a linha ( -l -pe ) nos arquivos transmitidos como argumentos ( -i ):

perl -i -l -pe '
    if (/from/) {                            # is the source text present on this line?
        printf STDERR ("%s: %s [y/N]? ", $ARGV, $_);  # display a prompt
        $r=<STDIN>;                                   # read user response
        if ($r =~ /^[Yy]/) {                          # if user entered Y:
            s/from/to/g;                              # replace all occurences on this line
    }' /path/to/files

Possíveis melhorias seriam colorir partes do prompt e suportar coisas como "substituir todas as ocorrências no arquivo atual". Solicitar separadamente cada ocorrência em uma linha seria mais difícil.

Segunda parte, combinando os arquivos. Se não houver muitos arquivos envolvidos e você estiver executando o zsh, será possível corresponder todos os arquivos no diretório atual e em seus subdiretórios de forma recursiva:

perl -i -l -pe '…' **/*(.)

Se o seu shell é bash ≥4, você pode executar perl … **/* , mas isso produzirá mensagens de erro espúrias porque o sed tentará (e falhará) rodar em diretórios. Se você quiser apenas realizar a substituição em um conjunto de arquivos, como arquivos C, poderá restringir as correspondências (que funcionam em bash ≥4 ou zsh):

perl -i -l -pe '…' **/*.[hc]

Se você precisar de um controle mais preciso sobre quais arquivos está substituindo, ou se o seu shell não tiver a construção de correspondência de diretório recursiva ** ou se você tiver muitos arquivos e receber um erro de "linha de comando muito longa", use find . Por exemplo, para executar uma substituição em todos os arquivos denominados *.h ou *.c no diretório atual e em seus subdiretórios (em sistemas mais antigos, talvez seja necessário usar \; em vez de + no final da linha ( o formulário + é mais rápido, mas não está disponível em todos os lugares).

find . -type f -name '*.[hc]' -exec perl -i -l -pe '…' {} +

Dito isso, eu ficaria com um editor interativo se você precisar de interação. Gert mostrou um maneira de fazer isso no Vim , embora exija a abertura de todos os arquivos pelos quais você deseja pesquisar, o que pode ser um problema se houver muito.

No Emacs, veja como você pode fazer isso:

  1. Reúna os nomes dos arquivos com M-x find-name-dired (especifique um diretório de nível superior) ou M-x find-dired (especifique uma linha de comando find arbitrária).
  2. No buffer resultante dired , pressione t para marcar todos arquivos, então Q ( dired-do-query-replace-regexp ) para execute uma substituição com aviso nos arquivos marcados.
por 03.05.2011 / 15:52
1

sdiff (consulte link ) pode ser útil aqui . Com ele você pode fazer patch interativo. Então, fazer isso com um arquivo temporário que você criou fazendo operações de substituição usando sed pode ser uma solução possível:

# use file descriptor 3 to still allow use of stdin
while IFS= read -r -d '' file <&3; do

  # write the result of the replacement into a temporary file
  sed -r 's/something/something_else/g' -- "$file" > replacer_tmp

  if cmp -s -- "$file" replacer_tmp; then
    continue; # nothing was replaced
  fi

  echo "There is something to replace in '$file'! Starting interactive diff."
  echo

  sdiff -o "$file" -s -d -- "$file" replacer_tmp

  echo

done 3< <(find . -type f -print0)

(Loop de arquivo usando substituição de processo não POSIX e read -d como suportado, por exemplo, por bash .)

    
por 13.03.2016 / 01:44