Remplacer des chaînes dans des fichiers en fonction de certains critères de recherche est une tâche très courante. Comment puis-je
- remplacer la chaîne
fooavecbardans tous les fichiers du répertoire courant ? - faire la même chose récursivement pour les sous-répertoires ?
- remplacer uniquement si le nom du fichier correspond à une autre chaîne ?
- remplacer uniquement si la chaîne est trouvée dans un certain contexte ?
- remplacer si la chaîne est sur un certain numéro de ligne ?
- remplacer plusieurs chaînes par le même remplacement
- remplacer plusieurs chaînes par des remplacements différents
Réponse acceptée :
1. Remplacement de toutes les occurrences d'une chaîne par une autre dans tous les fichiers du répertoire courant :
Ce sont pour les cas où vous savez que le répertoire ne contient que des fichiers normaux et que vous souhaitez traiter tous les fichiers non cachés. Si ce n'est pas le cas, utilisez les approches en 2.
Tous sed les solutions dans cette réponse supposent que GNU sed . Si vous utilisez FreeBSD ou macOS, remplacez -i avec -i '' . Notez également que l'utilisation du -i basculer avec n'importe quelle version de sed a certaines implications sur la sécurité du système de fichiers et est déconseillé dans tout script que vous envisagez de distribuer de quelque manière que ce soit.
-
Non récursif, fichiers de ce répertoire uniquement :
sed -i -- 's/foo/bar/g' * perl -i -pe 's/foo/bar/g' ./*
(le perl un échouera pour les noms de fichiers se terminant par | ou espace)).
-
Fichiers réguliers récursifs (y compris les fichiers cachés ) dans ce répertoire et tous les sous-répertoires
find . -type f -exec sed -i 's/foo/bar/g' {} +Si vous utilisez zsh :
sed -i -- 's/foo/bar/g' **/*(D.)(peut échouer si la liste est trop longue, voir
zargscontourner).Bash ne peut pas vérifier directement les fichiers normaux, une boucle est nécessaire (les accolades évitent de définir les options globalement) :
( shopt -s globstar dotglob; for file in **; do if [[ -f $file ]] && [[ -w $file ]]; then sed -i -- 's/foo/bar/g' "$file" fi done )Les fichiers sont sélectionnés lorsqu'il s'agit de fichiers réels (-f) et qu'ils sont accessibles en écriture (-w).
2. Ne remplacez que si le nom du fichier correspond à une autre chaîne / a une extension spécifique / est d'un certain type, etc :
-
Fichiers non récursifs de ce répertoire uniquement :
sed -i -- 's/foo/bar/g' *baz* ## all files whose name contains baz sed -i -- 's/foo/bar/g' *.baz ## files ending in .baz -
Fichiers réguliers et récursifs dans ce répertoire et tous les sous-répertoires
find . -type f -name "*baz*" -exec sed -i 's/foo/bar/g' {} +Si vous utilisez bash (les accolades évitent de définir les options globalement) :
( shopt -s globstar dotglob sed -i -- 's/foo/bar/g' **baz* sed -i -- 's/foo/bar/g' **.baz )Si vous utilisez zsh :
sed -i -- 's/foo/bar/g' **/*baz*(D.) sed -i -- 's/foo/bar/g' **/*.baz(D.)
Le -- sert à indiquer sed qu'aucun drapeau ne sera plus donné dans la ligne de commande. Ceci est utile pour se protéger contre les noms de fichiers commençant par - .
-
Si un fichier est d'un certain type, par exemple exécutable (voir
man findpour plus d'options):find . -type f -executable -exec sed -i 's/foo/bar/g' {} +
zsh :
sed -i -- 's/foo/bar/g' **/*(D*)
3. Remplacer uniquement si la chaîne est trouvée dans un certain contexte
-
Remplacez
fooavecbarseulement s'il y a unbazplus tard sur la même ligne :sed -i 's/foo(.*baz)/bar1/' file
Dans sed , en utilisant ( ) enregistre tout ce qui est entre parenthèses et vous pouvez ensuite y accéder avec 1 . Il existe de nombreuses variantes de ce thème, pour en savoir plus sur ces expressions régulières, voir ici.
-
Remplacez
fooavecbarseulement sifoose trouve sur la colonne 3d (champ) du fichier d'entrée (en supposant des champs séparés par des espaces) :gawk -i inplace '{gsub(/foo/,"baz",$3); print}' file
(nécessite gawk 4.1.0 ou plus récent).
-
Pour un champ différent, utilisez simplement
$NoùNest le numéro du champ d'intérêt. Pour un séparateur de champs différent (:dans cet exemple) utilisez :gawk -i inplace -F':' '{gsub(/foo/,"baz",$3);print}' file
Une autre solution utilisant perl :
perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@Fn"' foo
REMARQUE :à la fois le awk et perl les solutions affecteront l'espacement dans le fichier (supprimez les blancs de début et de fin et convertissez les séquences de blancs en un caractère d'espacement dans les lignes qui correspondent). Pour un champ différent, utilisez $F[N-1] où N est le numéro de champ que vous voulez et pour un séparateur de champ différent, utilisez (le $"=":" définit le séparateur de champ de sortie sur : ):
perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo
-
Remplacez
fooavecbaruniquement sur la 4ème ligne :sed -i '4s/foo/bar/g' file gawk -i inplace 'NR==4{gsub(/foo/,"baz")};1' file perl -i -pe 's/foo/bar/g if $.==4' file
4. Opérations de remplacement multiples :remplacer par des chaînes différentes
-
Vous pouvez combiner
sedcommandes :sed -i 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
Soyez conscient que l'ordre est important (sed 's/foo/bar/g; s/bar/baz/g' remplacera foo avec baz ).
-
ou commandes Perl
perl -i -pe 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file -
Si vous avez un grand nombre de motifs, il est plus facile d'enregistrer vos motifs et leurs remplacements dans un
sedfichier de script :#! /usr/bin/sed -f s/foo/bar/g s/baz/zab/g -
Ou, si vous avez trop de paires de modèles pour que ce qui précède soit réalisable, vous pouvez lire les paires de modèles à partir d'un fichier (deux modèles séparés par des espaces, $pattern et $replacement, par ligne) :
while read -r pattern replacement; do sed -i "s/$pattern/$replacement/" file done < patterns.txt -
Ce sera assez lent pour les longues listes de modèles et les gros fichiers de données, vous voudrez peut-être lire les modèles et créer un
sedscript d'eux à la place. Ce qui suit suppose un <espace> le délimiteur sépare une liste de MATCH<espace>REPLACE paires apparaissant une par ligne dans le fichierpatterns.txt:sed 's| *([^ ]*) *([^ ]*).*|s/1/2/g|' <patterns.txt | sed -f- ./editfile >outfile
Le format ci-dessus est largement arbitraire et, par exemple, ne permet pas un <espace> dans l'un des MATCH ou REPLACE . La méthode est cependant très générale :en gros, si vous pouvez créer un flux de sortie qui ressemble à un sed script, vous pouvez alors sourcer ce flux en tant que sed script en spécifiant sed le fichier de script sous la forme - stdin.
-
Vous pouvez combiner et concaténer plusieurs scripts de la même manière :
SOME_PIPELINE | sed -e'#some expression script' -f./script_file -f- -e'#more inline expressions' ./actual_edit_file >./outfile
Un sed POSIX concaténera tous les scripts en un seul dans l'ordre dans lequel ils apparaissent sur la ligne de commande. Aucun de ceux-ci n'a besoin de se terminer par un n ewline.
-
greppeut fonctionner de la même manière :sed -e'#generate a pattern list' <in | grep -f- ./grepped_file -
Lorsque vous travaillez avec des chaînes fixes en tant que modèles, il est recommandé d'échapper les métacaractères des expressions régulières . Vous pouvez le faire assez facilement :
sed 's/[]$&^*./[]/\&/g s| *([^ ]*) *([^ ]*).*|s/1/2/g| ' <patterns.txt | sed -f- ./editfile >outfile
5. Opérations de remplacement multiples :remplacez plusieurs modèles par la même chaîne
-
Remplacez l'un des
foo,baroubazavecfoobarsed -Ei 's/foo|bar|baz/foobar/g' file -
ou
perl -i -pe 's/foo|bar|baz/foobar/g' file