sed débutant: modification de toutes les occurrences d'un dossier

98

Je dois faire un regex rechercher et remplacer sur tous les fichiers dans un dossier (et ses sous-dossiers). Quelle serait la commande du shell Linux pour faire cela?

Par exemple, je veux exécuter ceci sur tous les fichiers et écraser l'ancien fichier avec le nouveau texte remplacé.

sed 's/old text/new text/g' 
nickf
la source

Réponses:

148

Il n'y a aucun moyen de le faire en utilisant uniquement sed. Vous devrez utiliser au moins l'utilitaire de recherche ensemble:

find . -type f -exec sed -i.bak "s/foo/bar/g" {} \;

Cette commande créera un .bakfichier pour chaque fichier modifié.

Remarques:

  • L' -iargument de la sedcommande est une extension GNU, donc, si vous exécutez cette commande avec le BSD, sedvous devrez rediriger la sortie vers un nouveau fichier puis le renommer.
  • L' findutilitaire n'implémente pas l' -execargument dans les anciennes boîtes UNIX, vous devrez donc utiliser un | xargs.
osantana
la source
4
A quoi ça sert \;?
Andriy Makukha
4
Nous devons dire pour trouver où la commande de l'argument -exec se termine par un «;». Mais le shell utilise le même symbole (;) comme séparateur de commandes shell, donc, nous devons échapper le ";" du shell pour le passer à l'argument -exec de find.
osantana
2
Il convient de noter que, -ien soi, ne crée pas de fichier de sauvegarde, et c'est ce qui pousse sed à effectuer l'opération sur le fichier en place.
Kyle
1
A quoi ça sert {}?
somenickname
1
Le {}sera remplacé par chaque nom de fichier trouvé par findet \;indique de trouver que la commande dont il a besoin pour exécuter se termine à ce stade.
osantana
51

Je préfère utiliser find | xargs cmdplusfind -exec parce que c'est plus facile à retenir.

Cet exemple remplace globalement «foo» par «bar» dans les fichiers .txt au niveau ou en dessous de votre répertoire actuel:

find . -type f -name "*.txt" -print0 | xargs -0 sed -i "s/foo/bar/g"

Les options -print0et -0peuvent être omises si vos noms de fichiers ne contiennent pas de caractères géniaux tels que des espaces.

Dennis
la source
3
Si vous êtes sur OSX, essayez find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' "s/foo/bar/g"(notez en fournissant une chaîne vide à l' -iargument).
Jakub Kukul
6

Pour la portabilité, je ne me fie pas aux fonctionnalités de sed spécifiques à Linux ou BSD. J'utilise plutôt le overwritescript du livre de Kernighan et Pike sur l'environnement de programmation Unix.

La commande est alors

find /the/folder -type f -exec overwrite '{}' sed 's/old/new/g' {} ';'

Et le overwritescript (que j'utilise partout) est

#!/bin/sh
# overwrite:  copy standard input to output after EOF
# (final version)

# set -x

case $# in
0|1)        echo 'Usage: overwrite file cmd [args]' 1>&2; exit 2
esac

file=$1; shift
new=/tmp/$$.new; old=/tmp/$$.old
trap 'rm -f $new; exit 1' 1 2 15    # clean up files

if "$@" >$new               # collect input
then
    cp $file $old   # save original file
    trap 'trap "" 1 2 15; cp $old $file     # ignore signals
          rm -f $new $old; exit 1' 1 2 15   # during restore
    cp $new $file
else
    echo "overwrite: $1 failed, $file unchanged" 1>&2
    exit 1
fi
rm -f $new $old

L'idée est qu'elle écrase un fichier uniquement si une commande réussit. Utile dans findet également là où vous ne voudriez pas utiliser

sed 's/old/new/g' file > file  # THIS CODE DOES NOT WORK

car le shell tronque le fichier avant de sedpouvoir le lire.

Norman Ramsey
la source
3

Puis-je suggérer (après avoir sauvegardé vos fichiers):

find /the/folder -type f -exec sed -ibak 's/old/new/g' {} ';'
paxdiablo
la source
0

Exemple: remplacez {AutoStart} par 1 pour tous les fichiers ini sous le dossier / app / config / et ses dossiers enfants:

sed 's/{AutoStart}/1/g' /app/config/**/*.ini
Alan Hu
la source
0
for i in $(ls);do sed -i 's/old_text/new_text/g' $i;done 
DimiDak
la source
5
Veuillez expliquer votre réponse.
Abandon le
Bien que ce code puisse résoudre le problème du PO, il est préférable d'inclure une explication sur la façon dont votre code résout le problème du PO. De cette façon, les futurs visiteurs peuvent apprendre de votre publication et l'appliquer à leur propre code. SO n'est pas un service de codage, mais une ressource de connaissances. Des réponses complètes et de haute qualité renforcent cette idée et sont plus susceptibles d'être votées. Ces fonctionnalités, ainsi que l'exigence que tous les messages soient autonomes, sont quelques atouts de SO en tant que plate-forme qui nous différencie des forums. Vous pouvez modifier pour ajouter des informations supplémentaires et / ou pour compléter vos explications avec la documentation source.
SherylHohman
1
Si vous ne pouvez pas lire ceci, oubliez ma réponse. Ce sont juste des bases bash.
DimiDak le
-1

Pourrait vouloir essayer mon script Perl de recherche / remplacement de masse . Présente certains avantages par rapport aux solutions d'utilité chaînée (comme ne pas avoir à gérer plusieurs niveaux d'interprétation des métacaractères du shell).

#!/usr/bin/perl

use strict;

use Fcntl qw( :DEFAULT :flock :seek );
use File::Spec;
use IO::Handle;

die "Usage: $0 startdir search replace\n"
    unless scalar @ARGV == 3;
my $startdir = shift @ARGV || '.';
my $search = shift @ARGV or
    die "Search parameter cannot be empty.\n";
my $replace = shift @ARGV;
$search = qr/\Q$search\E/o;

my @stack;

sub process_file($) {
    my $file = shift;
    my $fh = new IO::Handle;
    sysopen $fh, $file, O_RDONLY or
        die "Cannot read $file: $!\n";
    my $found;
    while(my $line = <$fh>) {
        if($line =~ /$search/) {
            $found = 1;
            last;
        }
    }
    if($found) {
        print "  Processing in $file\n";
        seek $fh, 0, SEEK_SET;
        my @file = <$fh>;
        foreach my $line (@file) {
            $line =~ s/$search/$replace/g;
        }
        close $fh;
        sysopen $fh, $file, O_WRONLY | O_TRUNC or
            die "Cannot write $file: $!\n";
        print $fh @file;
    }
    close $fh;
}

sub process_dir($) {
    my $dir = shift;
    my $dh = new IO::Handle;
    print "Entering $dir\n";
    opendir $dh, $dir or
        die "Cannot open $dir: $!\n";
    while(defined(my $cont = readdir($dh))) {
        next
            if $cont eq '.' || $cont eq '..';
        # Skip .swap files
        next
            if $cont =~ /^\.swap\./o;
        my $fullpath = File::Spec->catfile($dir, $cont);
        if($cont =~ /$search/) {
            my $newcont = $cont;
            $newcont =~ s/$search/$replace/g;
            print "  Renaming $cont to $newcont\n";
            rename $fullpath, File::Spec->catfile($dir, $newcont);
            $cont = $newcont;
            $fullpath = File::Spec->catfile($dir, $cont);
        }
        if(-l $fullpath) {
            my $link = readlink($fullpath);
            if($link =~ /$search/) {
                my $newlink = $link;
                $newlink =~ s/$search/$replace/g;
                print "  Relinking $cont from $link to $newlink\n";
                unlink $fullpath;
                my $res = symlink($newlink, $fullpath);
                warn "Symlink of $newlink to $fullpath failed\n"
                    unless $res;
            }
        }
        next
            unless -r $fullpath && -w $fullpath;
        if(-d $fullpath) {
            push @stack, $fullpath;
        } elsif(-f $fullpath) {
            process_file($fullpath);
        }
    }
    closedir($dh);
}

if(-f $startdir) {
    process_file($startdir);
} elsif(-d $startdir) {
    @stack = ($startdir);
    while(scalar(@stack)) {
        process_dir(shift(@stack));
    }
} else {
    die "$startdir is not a file or directory\n";
}
le chaos
la source
-3

Dans le cas où le nom des fichiers dans le dossier a des noms réguliers (comme fichier1, fichier2 ...), j'ai utilisé pour cycle.

for i in {1..10000..100}; do sed 'old\new\g' 'file'$i.xml > 'cfile'$i.xml; done
tereza
la source
ceci n'est pas lié à la question posée. La question ne mentionne rien sur le même modèle de nom de fichier / dossier. S'il vous plaît éviter de telles réponses
Kunal Parekh