En utilisant un shell comme bash ou zshell, comment puis-je effectuer une recherche et un remplacement récursifs? En d'autres termes, je souhaite remplacer chaque occurrence de "foo" par "bar" dans tous les fichiers de ce répertoire et de ses sous-répertoires.
Cette commande le fera (testé sur Mac OS X Lion et Kubuntu Linux).
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
Voici comment ça fonctionne:
find . -type f -name '*.txt'
trouve, dans le répertoire en cours (.
) et au-dessous, tous les fichiers ordinaires (-type f
) dont le nom se termine par .txt
|
passe le résultat de cette commande (une liste de noms de fichiers) à la commande suivantexargs
rassemble ces noms de fichiers et les remet un à un à sed
sed -i '' -e 's/foo/bar/g'
signifie "éditer le fichier à la place, sans sauvegarde, et effectuer le remplacement suivant (s/foo/bar
) plusieurs fois par ligne (/g
)" (voir man sed
)Notez que la partie "sans sauvegarde" de la ligne 4 me convient, car les fichiers que je modifie sont de toute façon sous contrôle de version. Je peux donc facilement les annuler en cas d'erreur.
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +
Cela supprime la dépendance xargs
.
Si vous utilisez Git, vous pouvez faire ceci:
git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'
-l
répertorie uniquement les noms de fichiers. -z
affiche un octet nul après chaque résultat.
J'ai fini par le faire, car certains fichiers d'un projet ne comportaient pas de nouvelle ligne à la fin du fichier et sed ajoutait une nouvelle ligne même si aucun autre changement n'était apporté. (Aucun commentaire sur si les fichiers doivent avoir une nouvelle ligne à la fin. ????)
Voici ma fonction zsh/Perl que j'utilise pour cela:
change () {
from=$1
shift
to=$1
shift
for file in $*
do
Perl -i.bak -p -e "s{$from}{$to}g;" $file
echo "Changing $from to $to in $file"
done
}
Et je l'exécuterais en utilisant
$ change foo bar **/*.Java
(par exemple)
Essayer:
sed -i 's/foo/bar/g' $(find . -type f)
Testé sur Ubuntu 12.04.
Cette commande NE fonctionnera PAS si les noms de sous-répertoires et/ou les noms de fichiers contiennent des espaces, mais si vous les avez, n'utilisez pas cette commande car elle ne fonctionnera pas.
Il est généralement déconseillé d'utiliser des espaces dans les noms de répertoire et les noms de fichiers.
http://linuxcommand.org/lc3_lts0020.php
Regardez "Faits importants sur les noms de fichiers"
J'utilise maintenant ce script Shell, qui combine des éléments des autres réponses et de la recherche sur le Web. Je l'ai placé dans un fichier appelé change
dans un dossier de mon $PATH
et j'ai fait chmod +x change
.
#!/bin/bash
function err_echo {
>&2 echo "$1"
}
function usage {
err_echo "usage:"
err_echo ' change old new foo.txt'
err_echo ' change old new foo.txt *.html'
err_echo ' change old new **\*.txt'
exit 1
}
[ $# -eq 0 ] && err_echo "No args given" && usage
old_val=$1
shift
new_val=$1
shift
files=$* # the rest of the arguments
[ -z "$old_val" ] && err_echo "No old value given" && usage
[ -z "$new_val" ] && err_echo "No new value given" && usage
[ -z "$files" ] && err_echo "No filenames given" && usage
for file in $files; do
sed -i '' -e "s/$old_val/$new_val/g" $file
done
Mon cas d'utilisation était que je voulais remplacer foo:/Drive_Letter
par foo:/bar/baz/xyz
. Dans mon cas, j'ai pu le faire avec le code suivant. J'étais dans le même répertoire que celui où se trouvaient la plupart des fichiers.
find . -name "*.library" -print0 | xargs -0 sed -i '' -e 's/foo:\/Drive_Letter:/foo:\/bar\/baz\/xyz/g'
espérons que cela a aidé.
La commande suivante a bien fonctionné sous Ubuntu et CentOS; Cependant, sous OS X, je continuais à avoir des erreurs:
find . -name Root -exec sed -i 's/1.2.3.4\/home/foo.com\/mnt/' {} \;
sed: 1: "./Root": code de commande non valide.
Lorsque j'ai essayé de passer les paramètres via xargs, cela a bien fonctionné, sans erreur:
find . -name Root -print0 | xargs -0 sed -i '' -e 's/1.2.3.4\/home/foo.com\/mnt/'