web-dev-qa-db-fra.com

Changer plusieurs fichiers

La commande suivante modifie correctement le contenu de 2 fichiers.

sed -i 's/abc/xyz/g' xaa1 xab1 

Mais ce que je dois faire est de modifier plusieurs fichiers de manière dynamique et je ne connais pas les noms de fichiers. Je veux écrire une commande qui lira tous les fichiers du répertoire actuel en commençant par xa* et sed devrait changer le contenu du fichier.

151
shantanuo

Mieux encore:

for i in xa*; do
    sed -i 's/asd/dfg/g' $i
done

car personne ne sait combien de fichiers sont présents et il est facile de dépasser les limites de la ligne de commande.

Voici ce qui se passe quand il y a trop de fichiers:

# grep -c aaa *
-bash: /bin/grep: Argument list too long
# for i in *; do grep -c aaa $i; done
0
... (output skipped)
#
114
lenik

Je suis surpris que personne n'ait mentionné l'argument -exec à trouver, qui est destiné à ce type de cas d'utilisation, bien qu'il lance un processus pour chaque nom de fichier correspondant:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} \;

Vous pouvez également utiliser xargs, qui invoquera moins de processus:

find . -type f -name 'xa*' | xargs sed -i 's/asd/dsg/g'

Ou plus simplement, utilisez +exec variante au lieu de ; dans find pour permettre à find de fournir plusieurs fichiers par appel de sous-processus:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} +
115
ealfonso

Vous pouvez utiliser grep et sed ensemble. Cela vous permet de rechercher des sous-répertoires de manière récursive.

Linux: grep -r -l <old> * | xargs sed -i 's/<old>/<new>/g'
OS X: grep -r -l <old> * | xargs sed -i '' 's/<old>/<new>/g'

For grep:
    -r recursively searches subdirectories 
    -l prints file names that contain matches
For sed:
    -i extension (Note: An argument needs to be provided on OS X)
75
Raj

Ces commandes ne fonctionneront pas dans la valeur par défaut sed fournie avec Mac OS X. 

De man 1 sed:

-i extension
             Edit files in-place, saving backups with the specified
             extension.  If a zero-length extension is given, no backup 
             will be saved.  It is not recommended to give a zero-length
             extension when in-place editing files, as you risk corruption
             or partial content in situations where disk space is exhausted, etc.

A tenté

sed -i '.bak' 's/old/new/g' logfile*

et

for i in logfile*; do sed -i '.bak' 's/old/new/g' $i; done

Les deux fonctionnent bien.

26
funroll

@PaulR a posté ceci en tant que commentaire, mais les gens devraient le voir comme une réponse (et cette réponse fonctionne mieux pour mes besoins):

sed -i 's/abc/xyz/g' xa*

Cela fonctionnera pour un nombre modéré de fichiers, probablement de l’ordre de dizaines, mais probablement pas de l’ordre de millions .

14
palswim

Une autre méthode plus polyvalente consiste à utiliser find:

sed -i 's/asd/dsg/g' $(find . -type f -name 'xa*')
6
dkinzer

J'utilise find pour une tâche similaire. C'est très simple: vous devez le passer comme argument pour sed comme ceci:

sed -i 's/EXPRESSION/REPLACEMENT/g' `find -name "FILE.REGEX"`

Ainsi, vous n'avez pas à écrire de boucles complexes et il est facile de savoir quels fichiers vous allez modifier. Il vous suffit d'exécuter find avant d'exécuter sed.

1
Bluesboy

Si vous êtes capable d'exécuter un script, voici ce que j'ai fait pour une situation similaire:

En utilisant un dictionnaire/hashMap (tableau associatif) et des variables pour la commande sed, nous pouvons parcourir le tableau en boucle pour remplacer plusieurs chaînes. L'inclusion d'un caractère générique dans le name_pattern permettra de remplacer in-situ dans les fichiers par un motif (cela pourrait ressembler à name_pattern='File*.txt') dans un répertoire spécifique (source_dir) . Tous les changements sont écrits dans la logfile dans le destin_dir

#!/bin/bash
source_dir=source_path
destin_dir=destin_path
logfile='sedOutput.txt'
name_pattern='File.txt'

echo "--Begin $(date)--" | tee -a $destin_dir/$logfile
echo "Source_DIR=$source_dir destin_DIR=$destin_dir "

declare -A pairs=( 
    ['WHAT1']='FOR1'
    ['OTHER_string_to replace']='string replaced'
)

for i in "${!pairs[@]}"; do
    j=${pairs[$i]}
    echo "[$i]=$j"
    replace_what=$i
    replace_for=$j
    echo " "
    echo "Replace: $replace_what for: $replace_for"
    find $source_dir -name $name_pattern | xargs sed -i "s/$replace_what/$replace_for/g" 
    find $source_dir -name $name_pattern | xargs -I{} grep -n "$replace_for" {} /dev/null | tee -a $destin_dir/$logfile
done

echo " "
echo "----End $(date)---" | tee -a $destin_dir/$logfile

Tout d'abord, le tableau de paires est déclaré, chaque paire est une chaîne de remplacement, puis WHAT1 sera remplacé pour FOR1 et OTHER_string_to replace sera remplacé pour string replaced dans le fichier File.txt. Dans la boucle, le tableau est lu, le premier membre de la paire est récupéré sous la forme replace_what=$i et le second sous la forme replace_for=$j. La commande find cherche dans le répertoire le nom du fichier (qui peut contenir un caractère générique) et la commande sed -i remplace dans le même fichier ce qui avait été défini précédemment. Enfin, j'ai ajouté une variable grep redirigée vers le fichier journal pour consigner les modifications apportées au (x) fichier (s). 

Cela a fonctionné pour moi dans GNU Bash 4.3sed 4.2.2 et est basé sur la réponse de VasyaNovikov pour Loop over tuples in bash .

tu peux faire

' xxxx ' text u recherche et le remplacera par ' yyyy '

grep -Rn '**xxxx**' /path | awk -F: '{print $1}' | xargs sed -i 's/**xxxx**/**yyyy**/'
0
Mohamed Galal