Como posso fazer uma localização e substituição recursiva na linha de comando?

Como posso fazer uma localização e substituição recursiva na linha de comando?

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:

  1. find . -type f -name '*.txt'encontra, no diretório atual ( .) e abaixo, todos os arquivos regulares ( -type f) cujos nomes terminam em.txt
  2. |passa a saída desse comando (uma lista de nomes de arquivos) para o próximo comando
  3. xargsreúne esses nomes de arquivos e os entrega um por um parased
  4. 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)" (ver man 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 xargsdependência.

Responder3

Se você estiver usando Git, você pode fazer isso:

git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'

-llista apenas nomes de arquivos. -zimprime 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)

informação relacionada