Como executar uma substituição no local sed que apenas cria backups de arquivos que foram alterados?

13

Executei o seguinte para substituir um termo usado em todos os arquivos no diretório de trabalho atual:

$ find . -type f -print0 | xargs -0 sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g'

Isso executou a substituição da palavra, mas também criou .buparquivos de arquivos que nunca tiveram a Ms. Johnsonstring.

Como faço a substituição sem criar todos esses backups desnecessários?

Belmin Fernandez
fonte
Parece-me como um bug de sed ver
Hotschke
Provavelmente, é possível usar uma abordagem melhor exe executar condicionalmente (apenas se o arquivo for alterado) :!cp '%' '%.bup'antes de salvar e sair. Pode valer a pena procurar dentro.
Curinga
Eu escrevi uma pergunta sobre minha idéia acima na vitroca de pilhas.
Curinga

Respostas:

6

Você pode verificar o conteúdo dos arquivos para garantir que uma substituição ocorra ao sedoperar com eles:

find . \
    -type f \
    -exec grep -q 'Ms. Johnson' {} \; \
    -print0 |
xargs -0 sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g'

Se você quer ser realmente esperto, pode deixar de usar find:

grep -Z -l -r 'Ms. Johnson' |
    xargs -0 sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g'
anfetamaquina
fonte
1
Eu acho que você precisa de um '{}'pouco antes de você \;nisso -exec.
Jander
8

O Find possui um -execoperador que executa um comando arbitrário. Ainda melhor, -execé um teste , para que você possa encadear vários -execs juntos e, se comandos anteriores falharem, outros não serão executados. A sequência {}é substituída pelo nome do arquivo atual e ;marca o final do comando. Você deve citar os dois para evitar que o shell interfira.

Então:

find . -type f \
    -exec grep -q 'Ms. Johnson' '{}' \; \
    -exec sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g' '{}' \;
Jander
fonte
Eu nunca tive um problema usando o bare {} 's.
Amphetamachine
1
@ anfetamaquina: Nem eu, mas a página do manual sugere. Pode ser uma coisa csh, ou pode haver shells (não Bash e POSIX) que se expandem {}para a cadeia nula ao fazer a expansão entre chaves.
Jander
4

Olhei para a página de manual e não vi nenhuma maneira de fazê-lo diretamente sed, como tenho certeza que você viu antes de perguntar. Vejo várias maneiras de contornar isso usando o grep, mas acho que o mais fácil é o seguinte:

grep -rlZ "Ms. Johnson" . | xargs -0 sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g'

-rrecursivo
-limprimir nome de arquivo apenas
-Znomes finais com nulo

Kevin
fonte
-1

Parece que você precisa criar uma lista de arquivos que correspondam aos seus termos de pesquisa primeiro.

search="test"
for match in $(find -type f -exec grep -l $search "{}" \;)
do sed -i'.bup' -e "/$search/ s/$search/Mrs. Melbin/g" "$match"
done
laebshade
fonte
Você esqueceu o .argumento como o primeiro find. Além disso, como fui informado há não muito tempo, você não precisa realmente citar ou escapar do{}
Kevin
Não gere uma lista de arquivos e execute a substituição de comandos. Primeiro, findgera uma lista de arquivos separados por nova linha, depois o shell divide essa lista em qualquer espaço em branco (não apenas nas novas linhas) e, em seguida, o shell trata cada palavra como um padrão global. Isso funciona apenas se os nomes dos arquivos não contiverem espaços em branco ou \[?*. Outras respostas nesta página mostram como fazer isso corretamente.
Gilles 'SO- stop be evil'
@ Kevin O .é desnecessário com o find (pelo menos, encontrado no linux) porque é assumido quando nenhum caminho é passado em um argumento.
Laebshade