web-dev-qa-db-fra.com

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

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.

68
Nathan Long

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:

  1. 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
  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 "é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.

87
Nathan Long
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +

Cela supprime la dépendance xargs.

24
Ztyx

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. ????)

8
David Winiecki

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)

3
Brian Agnew

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"

2
nexayq

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é 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
1
Nathan Long

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é.

0
neo7

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/'
0
John Morgan