Comment signaler les changements sur place «sed»

23

Lors de l'utilisation sedpour remplacer des chaînes en place, existe-t-il un moyen de lui faire rapporter les modifications qu'il fait (sans s'appuyer sur un diff d'anciens et de nouveaux fichiers)?

Par exemple, comment puis-je changer la ligne de commande

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

afin que je puisse voir les modifications qui sont apportées à la volée?

ricab
la source

Réponses:

15

Vous pouvez utiliser sedde » wdrapeau soit /dev/stderr, /dev/tty, /dev/fd/2si pris en charge sur votre système. Par exemple avec une entrée filecomme:

foo first
second: missing
third: foo
none here

fonctionnement

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

les sorties:

bar first
third: bar

bien que le filecontenu ait été changé en:

bar first
second: missing
third: bar
none here

Donc, dans votre cas, exécutez:

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

éditera les fichiers sur place et produira:

./fichier1:
trucs de bar
plus de bar

./fichier2:

./file3:
bar d'abord
troisième: bar

Vous pouvez également imprimer quelque chose comme original line >>> modified linepar exemple:

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

édite les fichiers sur place et les sorties:

./fichier1:
trucs de foo >>> trucs de bar
plus de foo >>> plus de bar

./fichier2:

./file3:
foo first >>> bar first
troisième: foo >>> troisième: bar
don_crissti
la source
16

Vous pouvez le faire en deux passes en utilisant l' paction rint sur la première passe avec:

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

--quietfait sed ne pas afficher toutes les lignes et le psuffixe affiche uniquement les lignes où la substitution a correspondu.

Cela a la limitation que sed ne montrera pas quels fichiers sont modifiés, lesquels pourraient bien sûr être corrigés avec une complexité supplémentaire.

msw
la source
Il ne fait pas tout à fait ce que je voulais puisque vous avez besoin de deux passes, mais je vote parce que je viens d'apprendre cette chose p et c'est très utile. Surtout si, comme vous l'avez dit, les fichiers ont également été imprimés. Quelque chose commefor x in `find . -type f`; do echo ///File $x: ; sed --quiet 's/abc/def/gp' $x; done
ricab
@msw J'ai plusieurs sedexpressions, je ne veux pas écrire /gppour chacune. Comment le définir globalement?
alhelal
8

Je ne pense pas que ce soit possible, mais une solution de contournement pourrait être d'utiliser à la place perl:

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

Cela imprimera les lignes modifiées à l'erreur standard. Par exemple:

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

Vous pouvez également rendre cela légèrement plus complexe, en imprimant le numéro de ligne, le nom du fichier, la ligne d'origine et la ligne modifiée:

$ find . -type f | 
   xargs perl -i -ne '$was=$_; chomp($was);
                      s/abc/def/ && print STDERR "$ARGV($.): $was : $_"' 
./foo(1): fooabcbar : foodefbar
terdon
la source
+1 Vous pouvez également utiliser $ARGVpour le nom du fichier utilisé.
Joseph R.
@JosephR. bon point, a ajouté.
terdon
Merci, c'est probablement utile (je ne l'ai pas testé moi-même). Je ne sélectionne pas car il n'utilise pas sed, donc il ne répond pas exactement à la question.
ricab
@ricab ça va, vous ne devez pas accepter une réponse à moins qu'elle ne réponde réellement à votre question. Gardez à l'esprit que pour des choses simples comme celle-ci, perlla syntaxe de est très similaire à celle de sedet je ne pense pas que ce que vous demandez soit réellement possible avec sed.
terdon
3

Il est possible d'utiliser l' indicateur w qui écrit le modèle actuel dans un fichier. Ainsi, en l'ajoutant à la commande de substitution, nous pouvons signaler des substitutions successives dans un fichier et l'imprimer une fois le travail terminé. J'aime aussi coloriser la chaîne remplacée par grep.

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

Notez qu'il ne doit y avoir qu'un seul espace entre le w et son nom de fichier.

C'est encore mieux que diff, car diffing peut également montrer des changements même s'ils n'ont pas été faits par sed.

Jack
la source
Je viens de le tester et sed écrit uniquement les lignes auxquelles il s'est substitué sed.done. Les lignes avec « To » dans le fichier d' origine ne sont donc pas à imprimer sed.done, donc lorsque vous grep "To" sed.donevous ne verrez que les lignes qui sont modifiés par sed. Vous ne verrez pas la ligne d'origine dans le fichier avant sa substitution, si c'est ce que vous visez ...
aairey
1

J'aime la solution @terdon - perl est bon pour cela.

Voici ma version modifiée qui:

  • n'essaiera pas de modifier les fichiers qui n'ont pas la chaîne correspondante
  • sauvegardera la version antérieure des fichiers qui changent (créer une version .bak)
  • listera chaque fichier / lineno modifié, et montrera les anciennes et nouvelles versions de cette ligne en retrait et en dessous pour une lecture facile

code

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:$_"'

exemple de sortie

/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
la source