Comment grep et remplacer

252

J'ai besoin de rechercher récursivement une chaîne spécifiée dans tous les fichiers et sous-répertoires d'un répertoire et de remplacer cette chaîne par une autre chaîne.

Je sais que la commande pour le trouver pourrait ressembler à ceci:

grep 'string_to_find' -r ./*

Mais comment puis-je remplacer chaque instance de string_to_findpar une autre chaîne?

billtian
la source
Je ne crois pas que grep puisse faire ça (je peux me tromper). Des moyens plus simples seraient d'utiliser sed ou perl pour effectuer le remplacement
Memento Mori
2
Essayez d'utilisersed -i 's/.*substring.*/replace/'
Eddy_Em
2
@Eddy_Em Cela remplacera la ligne entière par replace. Vous devez utiliser le regroupement pour capturer la partie de la ligne avant et après la sous-chaîne, puis la placer dans la ligne de remplacement. sed -i 's/\(.*\)substring\(.*\)/\1replace\2/'
JStrahl

Réponses:

249

Une autre option consiste à utiliser find puis à le passer par sed.

find /path/to/files -type f -exec sed -i 's/oldstring/new string/g' {} \;
rezizter
la source
34
Sur le terminal OS X 10.10, une chaîne d'extension appropriée au paramètre -iest requise. Par exemple, find /path/to/files -type f -exec sed -i "" "s/oldstring/new string/g" {} \;Quoi qu'il en soit, fournir une chaîne vide crée toujours un fichier de sauvegarde contrairement à ce qui est décrit dans le manuel ...
Eonil
10
Pourquoi ai-je «erreur sed: RE: séquence d'octets illégale». Et oui, j'ai ajouté le -i ""pour OS X. Cela fonctionne autrement.
taco
2
J'ai eu le problème de séquence d'octets illégale sur macOS 10.12, et cette question / réponse a résolu mon problème: stackoverflow.com/questions/19242275/… .
abeboparebop
3
Cela touche chaque fichier, donc les temps de fichiers sont modifiés; et convertit les fins de ligne de CRLFà LFsous Windows.
2017
183

J'ai eu la réponse.

grep -rl matchstring somedir/ | xargs sed -i 's/string1/string2/g'
billtian
la source
14
Ce serait parcourir les fichiers correspondants deux fois ... une fois avec greppuis à nouveau avec sed. L'utilisation de la findméthode est plus efficace mais cette méthode que vous mentionnez fonctionne.
cmevoli
41
Sous OS X, vous devrez passer sed -i 's/str1/str2/g'à sed -i "" 's/str1/str2/g'pour que cela fonctionne.
jdf
6
@cmevoli avec cette méthode, grepparcourt tous les fichiers et sedne scanne que les fichiers correspondants grep. Avec la findméthode dans l'autre réponse, findrépertorie d'abord tous les fichiers, puis sedanalyse tous les fichiers de ce répertoire. Cette méthode n'est donc pas nécessairement plus lente, elle dépend du nombre de correspondances et des différences de vitesse de recherche entre sed, grepet find.
joelostblom
4
OTOH de cette façon vous permet de PRÉVISUALISER ce que grep trouve avant de le remplacer, ce qui réduit considérablement le risque d'échec, en particulier pour les regex n00bs comme moi
Lennart Rolland
2
Ceci est également utile lorsque votre remplacement grep est plus intelligent que sed. Par exemple, ripgrep obéit à .gitignore tandis que sed ne le fait pas.
user31389
43

Vous pouvez même le faire comme ceci:

Exemple

grep -rl 'windows' ./ | xargs sed -i 's/windows/linux/g'

Cela recherchera la chaîne « windows » dans tous les fichiers relatifs au répertoire actuel et remplacera « windows » par « linux » pour chaque occurrence de la chaîne dans chaque fichier.

Dulith De Costa
la source
2
Le grepn'est utile que s'il existe des fichiers qui ne doivent pas être modifiés. L'exécution sedsur tous les fichiers mettra à jour la date de modification du fichier mais laissera le contenu inchangé s'il n'y a pas de correspondance.
tripleee
@tripleee: Soyez prudent avec ... mais [sed] laisse le contenu inchangé s'il n'y a pas de correspondances " . Lors de l'utilisation -i, je crois que sedchange l'heure du fichier de chaque fichier qu'il touche, même si le contenu est inchangé. sedconvertit également les fins de ligne sedCRLFLF
.Je
Cette commande a besoin d'un "" après le -i pour indiquer qu'aucun fichier de sauvegarde ne sera créé après la substitution sur place, au moins dans macosx. Consultez la page de manuel pour plus de détails. Si vous souhaitez une sauvegarde, c'est là que vous mettez l'extension du fichier à créer.
spinyBabbler
31

Cela fonctionne mieux pour moi sur OS X:

grep -r -l 'searchtext' . | sort | uniq | xargs perl -e "s/matchtext/replacetext/" -pi

Source: http://www.praj.com.au/post/23691181208/grep-replace-text-string-in-files

Marc Juchli
la source
c'est parfait! fonctionne également avec ag:ag "search" -l -r . | sort | uniq | xargs perl -e 's/search/replace' -pi
@sebastiankeller Votre commande Perl n'a pas la barre oblique finale, qui est une erreur de syntaxe.
tripleee
3
Pourquoi est-ce la sort -upartie paire de cela? Dans quelles circonstances vous attendriez-vous grep -rlà produire deux fois le même nom de fichier?
tripleee
5

D'autres solutions mélangent des syntaxes regex. Pour utiliser les modèles perl / PCRE à la fois pour la recherche et le remplacement, et pour traiter uniquement les fichiers correspondants, cela fonctionne assez bien:

grep -rlZPi 'match1' | xargs -0r perl -pi -e 's/match2/replace/gi;'

match1et match2sont généralement identiques mais match1peuvent être simplifiés pour supprimer des fonctionnalités plus avancées qui ne concernent que la substitution, par exemple la capture de groupes.

Traduction: greprécursivement et liste les fichiers qui correspondent à ce modèle PCRE, séparés par nul pour protéger tous les caractères spéciaux dans le nom de fichier, puis dirigez les noms de fichiers vers xargslesquels attend une liste séparée par nul, mais ne fera rien si aucun nom n'est reçu, et obtenirperl à remplacer les lignes où les correspondances sont trouvées.

Ajoutez le Icommutateur à greppour ignorer les fichiers binaires. Pour la correspondance sensible à la casse, supprimez le icommutateur de grepet l' iindicateur attaché à l'expression de substitution, mais pas le icommutateur sur perllui-même.

Walf
la source
Perl lui-même est tout à fait capable de récurer une structure de fichiers. En fait, il existe un outil find2perllivré avec Perl qui fait ce genre de chose sans aucune xargsastuce.
tripleee
@tripleee findne recherche pas le contenu des fichiers, et le but est de ne traiter que les fichiers correspondants sans écrire de programme Perl.
Walf
C'est une bonne solution pour Windows, car elle évite le problème des solutions basées sur sed de conversion des fins de ligne. Merci!
JamHandy
4

Généralement pas avec grep, mais plutôt avec sed -i 's/string_to_find/another_string/g'ou perl -i.bak -pe 's/string_to_find/another_string/g'.

minopret
la source
3

Soyez très prudent lors de l'utilisation findet seddans un dépôt git! Si vous n'excluez pas les fichiers binaires, vous pouvez vous retrouver avec cette erreur:

error: bad index file sha1 signature 
fatal: index file corrupt

Pour résoudre cette erreur, vous devez annuler le seden remplaçant votre new_stringpar votreold_string . Cela reviendra à vos chaînes remplacées, vous serez donc de retour au début du problème.

La bonne façon de rechercher une chaîne et de la remplacer est d'ignorer findet d'utiliser à la grepplace afin d'ignorer les fichiers binaires:

sed -ri -e "s/old_string/new_string/g" $(grep -Elr --binary-files=without-match "old_string" "/files_dir")

Crédits pour @hobs

tsveti_iko
la source
1

Voici ce que je ferais:

find /path/to/dir -type f -iname "*filename*" -print0 | xargs -0 sed -i '/searchstring/s/old/new/g'

cela va rechercher tous les fichiers contenant filenamedans le nom du fichier sous le /path/to/dir, que pour chaque fichier trouvé, recherchez la ligne avec searchstringet remplacez oldpar new.

Cependant, si vous voulez omettre de rechercher un fichier spécifique avec une filenamechaîne dans le nom du fichier, faites simplement:

find /path/to/dir -type f -print0 | xargs -0 sed -i '/searchstring/s/old/new/g'

Cela fera la même chose ci-dessus, mais pour tous les fichiers trouvés sous /path/to/dir.

tinnick
la source
0

Une autre option serait d'utiliser simplement perl avec globstar.

L'activation shopt -s globstardans votre .bashrc(ou n'importe où) permet au **modèle glob de correspondre à tous les sous-répertoires et fichiers de manière récursive.

Ainsi, l'utilisation perl -pXe 's/SEARCH/REPLACE/g' -i **sera récursivement remplacée SEARCHpar REPLACE.

Le -Xdrapeau indique à perl de "désactiver tous les avertissements" - ce qui signifie qu'il ne se plaindra pas des répertoires.

Le globstar vous permet également de faire des choses comme sed -i 's/SEARCH/REPLACE/g' **/*.extsi vous vouliez remplacer SEARCHavec REPLACEdans tous les fichiers enfants avec l'extension .ext.

GuiltyDolphin
la source
"Une autre option serait d'utiliser simplement perl avec globstar ..." - Pas sur les machines Posixy, comme Solaris. C'est pourquoi je recherche spécifiquement grepet sed.
2017