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
foo
avecbar
dans 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
zargs
contourner).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 find
pour 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
foo
avecbar
seulement s'il y a unbaz
plus 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
foo
avecbar
seulement sifoo
se 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
$N
oùN
est 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
foo
avecbar
uniquement 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
sed
commandes :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
sed
fichier 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
sed
script 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.
-
grep
peut 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
,bar
oubaz
avecfoobar
sed -Ei 's/foo|bar|baz/foobar/g' file
-
ou
perl -i -pe 's/foo|bar|baz/foobar/g' file