Como relatar alterações no local "sed"

23

Ao usar sedpara substituir seqüências de caracteres no local, existe uma maneira de fazer com que ele relate as alterações que faz (sem depender de uma comparação de arquivos antigos e novos)?

Por exemplo, como posso alterar a linha de comando

find . -type f | xargs sed -i 's/abc/def/g'

para que eu possa ver as alterações que são feitas em tempo real?

ricab
fonte

Respostas:

15

Você pode usar sed's wmarque com quer /dev/stderr, /dev/tty, /dev/fd/2se suportada pelo seu sistema. Por exemplo, com uma entrada filecomo:

foo first
second: missing
third: foo
none here

corrida

sed -i '/foo/{
s//bar/g
w /dev/stdout
}' file

saídas:

bar first
third: bar

embora o fileconteúdo tenha sido alterado para:

bar first
second: missing
third: bar
none here

Portanto, no seu caso, executando:

find . -type f -printf '\n%p:\n' -exec sed -i '/foo/{
s//bar/g
w /dev/fd/2
}' {} \;

irá editar os arquivos no local e na saída:

./arquivo1:
coisas de bar
mais bar

./file2:

./file3:
bar primeiro
terceiro: bar

Você também pode imprimir algo como, original line >>> modified linepor exemplo:

find . -type f -printf '\n%p:\n' -exec sed -i '/foo/{
h
s//bar/g
H
x
s/\n/ >>> /
w /dev/fd/2
x
}' {} \;

edita os arquivos no local e produz:

./arquivo1:
coisas foo >>> coisas bar
mais foo >>> mais bar

./file2:

./file3:
foo primeiro >>> barra primeiro
terceiro: foo >>> terceiro: bar
don_crissti
fonte
16

Você pode fazer isso em duas passagens usando a pação rint na primeira passagem com:

find . -type f | xargs sed --quiet 's/abc/def/gp'

where --quietfaz sed não mostrar todas as linhas e o psufixo mostra apenas as linhas nas quais a substituição correspondeu.

Isso tem a limitação de que o sed não mostrará quais arquivos estão sendo alterados e, é claro, que poderiam ser corrigidos com alguma complexidade adicional.

msw
fonte
Não é exatamente o que eu queria, pois você precisa de dois passes, mas estou votando porque aprendi essa coisa de p e é muito útil. Especialmente se, como você disse, os arquivos também foram impressos. Algo comofor x in `find . -type f`; do echo ///File $x: ; sed --quiet 's/abc/def/gp' $x; done
ricab 23/10/2013
@ msw Tenho muitas sedexpressões, não quero escrever /gppara cada uma. Como configurá-lo globalmente?
Alhelal
8

Eu não acho que isso é possível, mas uma solução alternativa pode ser usar perl:

find . -type f | xargs perl -i -ne 's/abc/def/ && print STDERR' 

Isso imprimirá as linhas alteradas para erro padrão. Por exemplo:

$ cat foo
fooabcbar
$ find . -type f | xargs perl -i -ne 's/abc/def/ && print STDERR' 
foodefbar

Você também pode tornar isso um pouco mais complexo, imprimindo o número da linha, o nome do arquivo, a linha original e a linha alterada:

$ find . -type f | 
   xargs perl -i -ne '$was=$_; chomp($was);
                      s/abc/def/ && print STDERR "$ARGV($.): $was : $_"' 
./foo(1): fooabcbar : foodefbar
terdon
fonte
+1 Você também pode usar $ARGVo nome do arquivo que está sendo operado.
Joseph R.
@JosephR. bom ponto, acrescentou.
terdon
Obrigado, isso provavelmente é útil (não testei por conta própria). Não estou selecionando, pois ele não usa sed, portanto não responde exatamente à pergunta.
Ricab # 23/13
@ricab, tudo bem, você não deve aceitar uma resposta, a menos que ela realmente responda à sua pergunta. Porém, lembre-se de que, para coisas simples como essa, perla sintaxe é muito semelhante à sedda e não acho que o que você está pedindo seja realmente possível sed.
terdon
3

É possível usar o sinalizador w que grava o padrão atual em um arquivo. Portanto, adicionando-o ao comando substitute, podemos relatar substituições sucessivas em um arquivo e imprimi-lo após a conclusão do trabalho. Também gosto de colorir a sequência substituída com grep.

sed -i -e "s/From/To/gw /tmp/sed.done" file_name
grep --color -e "To" /tmp/sed.done

Observe que deve haver apenas um espaço entre we seu nome de arquivo.

Isso é ainda melhor que o diff, pois o diffing também pode mostrar alterações mesmo que não tenham sido feitas pelo sed.

Jack
fonte
Apenas testei e o sed apenas escreve as linhas que ele substituiu sed.done. As linhas com "To" no arquivo original, portanto, não são impressos para sed.done, assim, quando você grep "To" sed.donesó vai ver as linhas que são alteradas por sed. Você não verá a linha original no arquivo antes ele foi substituído, se é isso que você está visando ...
aairey
1

Eu gosto da solução @terdon - o perl é bom para isso.

Aqui está minha versão aprimorada que:

  • não tentará alterar arquivos que não possuem a string correspondente
  • fará backup da versão anterior dos arquivos que mudam (crie uma versão .bak)
  • listará todos os arquivos / lineno alterados e mostrará as versões OLD e NEW dessa linha recuada e embaixo para facilitar a leitura

código

find /tmp/test -type f ! -name "*.bak" -exec grep -l '/opt/gridmon' {} \; | xargs -L1 perl -ni'.bak' -e'$old=$_; s/\/opt\/gridmon/~/g && print STDERR "$ARGV($.):\n\tOLD:$old\tNEW:$_"'

saída de exemplo

/tmp/test/test4.cfg(13):
    OLD:    ENVFILE /opt/gridmon/server/etc/gridmonserver.cfg
    NEW:    ENVFILE ~/server/etc/gridmonserver.cfg
/tmp/test/test4.cfg(24):
    OLD:    ENVFILE /opt/gridmon/server/etc/gridmonserver.cfg
    NEW:    ENVFILE ~/server/etc/gridmonserver.cfg
andy987
fonte