Comment effectuer une substitution sed sur place qui ne crée que des sauvegardes des fichiers qui ont été modifiés?

13

J'ai exécuté ce qui suit pour remplacer un terme utilisé dans tous les fichiers du répertoire de travail actuel:

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

Cela a effectué la substitution de mots mais a également créé des .bupfichiers de fichiers qui n'ont jamais eu la Ms. Johnsonchaîne.

Comment effectuer la substitution sans créer toutes ces sauvegardes inutiles?

Belmin Fernandez
la source
me semble être un bug de sed see
Hotschke
Il existe probablement une meilleure approche possible en utilisant exet conditionnellement (uniquement si le fichier est modifié) en cours d'exécution :!cp '%' '%.bup'avant l'enregistrement et la fermeture. Il serait peut-être intéressant d'y regarder de plus près.
Wildcard
J'ai écrit une question sur mon idée ci-dessus sur l' viéchange de pile.
Wildcard

Réponses:

6

Vous pouvez vérifier le contenu des fichiers pour vous assurer qu'une substitution aura lieu lors sedde leur fonctionnement:

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

Si vous voulez vraiment être intelligent, vous pouvez renoncer à utiliser finddu tout:

grep -Z -l -r 'Ms. Johnson' |
    xargs -0 sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g'
amphétamachine
la source
1
Je pense que vous avez besoin d' une '{}'juste avant \;dans ce -exec.
Jander
8

Find possède un -execopérateur qui exécute une commande arbitraire. Encore mieux, -execest un test , donc vous pouvez enchaîner plusieurs -execs ensemble, et si les commandes précédentes échouent, les dernières ne seront pas exécutées. La chaîne {}est remplacée par le nom de fichier actuel et ;marque la fin de la commande. Vous devez citer les deux pour éviter que le shell interfère.

Donc:

find . -type f \
    -exec grep -q 'Ms. Johnson' '{}' \; \
    -exec sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g' '{}' \;
Jander
la source
Je n'ai jamais eu de problème en utilisant des {}} nus.
amphetamachine
1
@amphetamachine: Moi non plus, mais la page de manuel le suggère. Cela pourrait être une chose csh, ou il pourrait y avoir des shells (pas Bash et non POSIX) qui se développent {}dans la chaîne nulle lors de l'expansion de l'accolade.
Jander
4

J'ai regardé la page de manuel et je n'ai vu aucun moyen de le faire directement sed, comme je suis sûr que vous l'avez fait avant de le demander. Je vois plusieurs façons de contourner cela en utilisant grep, mais je pense que le plus simple est le suivant:

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

-rrecurse
-lprint nom de fichier uniquement les
-Znoms de fin avec null

Kevin
la source
-1

Il semble que vous devez d'abord créer une liste de fichiers qui correspondent à vos termes de recherche.

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
abat-jour
la source
Vous avez oublié le .comme premier argument find. De plus, comme j'ai été informé moi-même il n'y a pas si longtemps, vous n'avez pas vraiment besoin de citer ou d'échapper à{}
Kevin
Ne générez pas de liste de fichiers et n'effectuez pas de substitution de commande à ce sujet. findGénère d' abord une liste de fichiers séparés par des sauts de ligne, puis le shell décompose cette liste dans n'importe quel espace (pas seulement les retours à la ligne), puis le shell traite chaque mot comme un modèle global. Cela ne fonctionne que si vos noms de fichiers ne contiennent pas d'espaces ou \[?*. D'autres réponses sur cette page montrent comment procéder correctement.
Gilles 'SO- arrête d'être méchant'
@Kevin .n'est pas nécessaire avec find (au moins find trouvé sur linux) car il est supposé qu'aucun argument ne passe par aucun chemin.
laebshade