
Usando um shell como bash ou zshell, como posso fazer uma 'localização e substituição' recursiva? Em outras palavras, quero substituir todas as ocorrências de 'foo' por 'bar' em todos os arquivos deste diretório e seus subdiretórios.
Responder1
Este comando fará isso (testado no Mac OS X Lion e no Kubuntu Linux).
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
Veja como funciona:
find . -type f -name '*.txt'
encontra, no diretório atual (.
) e abaixo, todos os arquivos regulares (-type f
) cujos nomes terminam em.txt
|
passa a saída desse comando (uma lista de nomes de arquivos) para o próximo comandoxargs
reúne esses nomes de arquivos e os entrega um por um parased
sed -i '' -e 's/foo/bar/g'
significa "editar o arquivo no local, sem backup, e fazer a seguinte substituição (s/foo/bar
) várias vezes por linha (/g
)" (verman sed
)
Observe que a parte 'sem backup' na linha 4 está OK para mim, porque os arquivos que estou alterando estão sob controle de versão de qualquer maneira, então posso desfazer facilmente se houver um erro.
Para evitar ter que lembrar disso, eu uso um script bash interativo, como segue:
#!/bin/bash
# find_and_replace.sh
echo "Find and replace in current directory!"
echo "File pattern to look for? (eg '*.txt')"
read filepattern
echo "Existing string?"
read existing
echo "Replacement string?"
read replacement
echo "Replacing all occurences of $existing with $replacement in files matching $filepattern"
find . -type f -name $filepattern -print0 | xargs -0 sed -i '' -e "s/$existing/$replacement/g"
Responder2
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +
Isso remove a xargs
dependência.
Responder3
Se você estiver usando Git, você pode fazer isso:
git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'
-l
lista apenas nomes de arquivos. -z
imprime um byte nulo após cada resultado.
Acabei fazendo isso porque alguns arquivos em um projeto não tinham uma nova linha no final do arquivo e o sed adicionou uma nova linha mesmo quando não fez outras alterações. (Nenhum comentário sobre se os arquivos devem ou não ter uma nova linha no final.
Responder4
Aqui está minha função zsh/perl que uso para isso:
change () {
from=$1
shift
to=$1
shift
for file in $*
do
perl -i.bak -p -e "s{$from}{$to}g;" $file
echo "Changing $from to $to in $file"
done
}
E eu executaria usando
$ change foo bar **/*.java
(por exemplo)