Comment puis-je effectuer une recherche récursive et la remplacer à partir de la ligne de commande?

84

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.

Nathan Long
la source
Une réponse alternative pour les mêmes questions se trouvent ici stackoverflow.com/questions/9704020/...
dunxd
Ce serait peut-être une bonne idée d’essayer cela avec vim. De cette façon, vous pouvez utiliser la fonction de confirmation pour vous assurer de ne pas échanger quelque chose que vous n'avez pas l'intention de faire. Je ne suis pas sûr si cela peut être fait à l'échelle du répertoire.
Samy Bencherif le

Réponses:

106

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'

Voilà comment cela fonctionne:

  1. find . -type f -name '*.txt'trouve, dans le répertoire courant ( .) et au-dessous, tous les fichiers normaux ( -type f) dont le nom se termine par.txt
  2. | passe le résultat de cette commande (une liste de noms de fichiers) à la commande suivante
  3. xargs rassemble ces noms de fichiers et les remet un à un à sed
  4. sed -i '' -e 's/foo/bar/g'signifie "édite le fichier en place, sans sauvegarde, et effectue la substitution suivante ( 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.

Nathan Long
la source
9
Ne jamais diriger la sortie vers xargs sans l' -print0option. Votre commande échouera sur les fichiers avec des espaces, etc. dans leur nom.
Slhck
20
En outre, find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +nous ferons tout cela avec GNU Find.
Daniel Andersson
6
Je reçois sed: can't read : No such file or directoryquand je cours find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g', mais find . -name '*.md' -print0donne une liste de nombreux fichiers.
Martin Thoma
9
Cela fonctionne pour moi si je supprime l'espace entre -ile''
Canadien Luke REINSTATE MONICA le
3
Quel est le sens de ''après le sed -iquel est le ''rôle?
Jas
27
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +

Cela supprime la xargsdépendance.

Ztyx
la source
4
Cela ne fonctionne pas avec GNU sedet échouera donc sur la plupart des systèmes. GNU sedvous demande de ne pas mettre d’espace entre -iet ''.
Slhck
1
Les réponses acceptées l'expliquent mieux. mais +1 pour utiliser la syntaxe correcte.
Orodbhen
9

Si vous utilisez Git, vous pouvez faire ceci:

git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'

-lrépertorie uniquement les noms de fichiers. -zaffiche 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é. (Pas de commentaire sur le point de savoir si les fichiers doivent avoir une nouvelle ligne à la fin.)

David Winiecki
la source
1
Big +1 pour cette solution. Le reste des find ... -print0 | xargs -0 sed ...solutions prend non seulement beaucoup plus de temps, mais ajoute également des lignes nouvelles aux fichiers qui n'en ont pas déjà, ce qui est une gêne lors du travail dans un dépôt Git. git grepest éclairant rapidement par comparaison.
Josh Kupershmidt
3

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)

Brian Agnew
la source
2

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"

Nexayq
la source
Essayez-le lorsque vous avez des fichiers avec des espaces dans leurs noms. (Il existe une règle empirique qui dit: "Si quelque chose semble trop beau pour être vrai, c'est probablement le cas." Si vous avez "découvert" une solution plus compacte que tout ce que quelqu'un d'autre a posté depuis 3 ans et demi, vous devriez vous demander pourquoi cela pourrait être.)
Scott
1

Utiliser ce script shell

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é changedans un dossier sur mon ordinateur $PATHet l'a 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
Nathan Long
la source
0

Mon cas d'utilisation était que je voulais remplacer foo:/Drive_Letterpar. 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é.

neo7
la source
0

La commande suivante a bien fonctionné sous Ubuntu et CentOS; Cependant, sous OS XI, les erreurs continuaient à se produire:

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/'
John Morgan
la source
Le fait que vous ayez changé -ipour -i ''est probablement plus pertinent que le fait que vous avez changé -execpour -print0 | xargs -0. BTW, vous n'avez probablement pas besoin de la -e.
Scott
0
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'

Ce qui précède a fonctionné comme un charme, mais avec les répertoires liés, je dois y ajouter un -Ldrapeau. La version finale ressemble à:

# Recursively find and replace in files
find -L . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
sMajeed
la source