Usando um shell como o bash ou o zshell, como posso fazer um 'localizar e substituir' recursivo? Em outras palavras, quero substituir cada ocorrência de 'foo' por 'bar' em todos os arquivos neste diretório e em seus subdiretórios.
Usando um shell como o bash ou o zshell, como posso fazer um 'localizar e substituir' recursivo? Em outras palavras, quero substituir cada ocorrência de 'foo' por 'bar' em todos os arquivos neste diretório e em seus subdiretórios.
Respostas:
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 isso 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 comando xargs
reúne esses nomes de arquivos e os entrega um por um para sed
sed -i '' -e 's/foo/bar/g'
significa "editar o arquivo no lugar, sem um backup, e fazer a seguinte substituição ( s/foo/bar
) várias vezes por linha ( /g
)" (Vejo man sed
) Note que a parte "sem um backup" na linha 4 está OK para mim, porque os arquivos que estou alterando estão sob controle de versão, assim eu posso facilmente desfazer se houve um erro.
-print0
opção. Seu comando falhará em arquivos com espaços, etc. em seus nomes.
find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +
fará tudo isso com o GNU find.
sed: can't read : No such file or directory
quando eu corro find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g'
, mas find . -name '*.md' -print0
fornece uma lista de muitos arquivos.
-i
e a ''
''
depois de sed -i
o que é ''
Função?
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +
Isso remove o xargs
dependência.
sed
, então falhará na maioria dos sistemas. GNU sed
requer que você não coloque nenhum espaço entre -i
e ''
.
Se você está usando o Git, então 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.)
find ... -print0 | xargs -0 sed ...
As soluções não só levam muito mais tempo, mas também adicionam novas linhas aos arquivos que já não o possuem, o que é um incômodo ao trabalhar dentro de um repositório do git. git grep
é iluminação rápida por comparação.
Aqui está a minha função zsh / perl que eu 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 iria executá-lo usando
$ change foo bar **/*.java
(por exemplo)
Experimentar:
sed -i 's/foo/bar/g' $(find . -type f)
Testado no Ubuntu 12.04.
Este comando NÃO funcionará se nomes de subdiretórios e / ou nomes de arquivos contiverem espaços, mas se você os tiver, não use este comando, pois ele não funcionará.
Geralmente, é uma prática ruim usar espaços em nomes de diretórios e nomes de arquivos.
http://linuxcommand.org/lc3_lts0020.php
Veja "Fatos importantes sobre nomes de arquivos"
Agora eu uso esse script de shell, que combina as coisas que aprendi com as outras respostas e com a pesquisa na web. Coloquei em um arquivo chamado change
em uma pasta no meu $PATH
e fez chmod +x change
.
#!/bin/bash
function err_echo {
>&2 echo "$1"
}
function usage {
err_echo "usage:"
err_echo ' change old new foo.txt'
err_echo ' change old new foo.txt *.html'
err_echo ' change old new **\*.txt'
exit 1
}
[ $# -eq 0 ] && err_echo "No args given" && usage
old_val=$1
shift
new_val=$1
shift
files=$* # the rest of the arguments
[ -z "$old_val" ] && err_echo "No old value given" && usage
[ -z "$new_val" ] && err_echo "No new value given" && usage
[ -z "$files" ] && err_echo "No filenames given" && usage
for file in $files; do
sed -i '' -e "s/$old_val/$new_val/g" $file
done
Meu caso de uso era eu queria substituir foo:/Drive_Letter
com foo:/bar/baz/xyz
No meu caso, consegui fazer isso com o seguinte código.
Eu estava no mesmo local de diretório onde havia muitos arquivos.
find . -name "*.library" -print0 | xargs -0 sed -i '' -e 's/foo:\/Drive_Letter:/foo:\/bar\/baz\/xyz/g'
Espero que tenha ajudado.
O seguinte comando funcionou bem no Ubuntu e no CentOS; no entanto, no OS X, continuei recebendo erros:
find . -name Root -exec sed -i 's/1.2.3.4\/home/foo.com\/mnt/' {} \;
sed: 1: "./Root": código de comando inválido.
Quando tentei passar os params via xargs funcionou bem sem erros:
find . -name Root -print0 | xargs -0 sed -i '' -e 's/1.2.3.4\/home/foo.com\/mnt/'
-i
para -i ''
é provavelmente mais relevante do que o fato de você ter mudado -exec
para -print0 | xargs -0
. BTW, você provavelmente não precisa do -e
.