sed Anfänger: Ändern aller Vorkommen in einem Ordner

98

Ich muss eine Regex-Suche und Ersetzung für alle Dateien in einem Ordner (und seinen Unterordnern) durchführen. Was wäre der Linux-Shell-Befehl, um das zu tun?

Zum Beispiel möchte ich dies über alle Dateien ausführen und die alte Datei mit dem neuen, ersetzten Text überschreiben.

sed 's/old text/new text/g' 
nickf
quelle

Antworten:

146

Es gibt keine Möglichkeit, dies nur mit sed zu tun. Sie müssen mindestens das Suchdienstprogramm zusammen verwenden:

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

Dieser Befehl erstellt eine .bakDatei für jede geänderte Datei.

Anmerkungen:

  • Das -iArgument für den sedBefehl ist eine GNU-Erweiterung. Wenn Sie diesen Befehl mit den BSDs ausführen, müssen sedSie die Ausgabe in eine neue Datei umleiten und dann umbenennen.
  • Das findDienstprogramm implementiert das -execArgument nicht in alten UNIX-Boxen, daher müssen Sie | xargsstattdessen ein verwenden.
Osantana
quelle
3
Wofür ist \;?
Andriy Makukha
4
Wir müssen sagen, wo der Befehl des Arguments -exec mit einem ";" endet. Die Shell verwendet jedoch dasselbe Symbol (;) wie ein Shell-Befehlstrennzeichen. Daher müssen wir uns dem ";" von der Shell, um es an das Argument -exec des Find zu übergeben.
Osantana
2
Es ist erwähnenswert, dass -iselbst keine Sicherungsdatei erstellt wird und sed den Vorgang für die vorhandene Datei ausführt.
Kyle
1
Wofür ist {}?
Somenickname
1
Das {}wird durch jeden Dateinamen ersetzt, der von gefunden wird, findund \;weist darauf hin, dass der Befehl, den er ausführen muss, an diesem Punkt beendet ist.
Osantana
50

Ich ziehe es zu benutzen find | xargs cmdüber , find -execweil es leichter zu merken.

In diesem Beispiel wird "foo" global durch "bar" in TXT-Dateien in oder unter Ihrem aktuellen Verzeichnis ersetzt:

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

Die Optionen -print0und können -0weggelassen werden, wenn Ihre Dateinamen keine funky Zeichen wie Leerzeichen enthalten.

Dennis
quelle
3
Wenn Sie unter OSX arbeiten, versuchen Sie es find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' "s/foo/bar/g"(beachten Sie, dass das -iArgument eine leere Zeichenfolge enthält ).
Jakub Kukul
5

Für die Portabilität verlasse ich mich nicht auf Funktionen von sed, die spezifisch für Linux oder BSD sind. Stattdessen verwende ich das overwriteSkript aus Kernighans und Pikes Buch über die Unix-Programmierumgebung.

Der Befehl lautet dann

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

Und das overwriteSkript (das ich überall benutze) ist

#!/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

Die Idee ist, dass eine Datei nur überschrieben wird, wenn ein Befehl erfolgreich ist. Nützlich in findund auch dort, wo Sie nicht verwenden möchten

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

weil die Shell die Datei abschneidet, bevor sedsie gelesen werden kann.

Norman Ramsey
quelle
3

Darf ich vorschlagen (nach dem Sichern Ihrer Dateien):

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

Beispiel: Ersetzen Sie {AutoStart} mit 1 für alle INI-Dateien im Ordner / app / config / und seinen untergeordneten Ordnern:

sed 's/{AutoStart}/1/g' /app/config/**/*.ini
Alan Hu
quelle
0
for i in $(ls);do sed -i 's/old_text/new_text/g' $i;done 
DimiDak
quelle
5
Bitte erläutern Sie Ihre Antwort.
Ausfall
Während dieser Code das Problem des OP lösen kann, ist es besser, eine Erklärung beizufügen, wie Ihr Code das Problem des OP behebt. Auf diese Weise können zukünftige Besucher aus Ihrem Beitrag lernen und ihn auf ihren eigenen Code anwenden. SO ist kein Codierungsdienst, sondern eine Ressource für Wissen. Hochwertige, vollständige Antworten untermauern diese Idee und werden eher positiv bewertet. Diese Funktionen sowie die Anforderung, dass alle Beiträge in sich geschlossen sein müssen, sind einige Stärken von SO als Plattform, die uns von Foren unterscheidet. Sie können bearbeiten, um zusätzliche Informationen hinzuzufügen und / oder Ihre Erklärungen durch Quellendokumentation zu ergänzen.
SherylHohman
Wenn Sie dies nicht lesen können, vergessen Sie einfach meine Antwort. Es sind nur Bash-Grundlagen.
DimiDak
-1

Vielleicht möchten Sie mein Massensuch- / Ersetzungs-Perl-Skript ausprobieren . Hat einige Vorteile gegenüber verketteten Utility-Lösungen (z. B. dass nicht mehrere Ebenen der Shell-Metazeichen-Interpretation behandelt werden müssen).

#!/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";
}
Chaos
quelle
-3

Falls der Name der Dateien im Ordner einige reguläre Namen hat (wie Datei1, Datei2 ...), die ich für den Zyklus verwendet habe.

for i in {1..10000..100}; do sed 'old\new\g' 'file'$i.xml > 'cfile'$i.xml; done
Tereza
quelle
Dies hängt nicht mit der gestellten Frage zusammen. In der Frage wird nichts über dasselbe Datei- / Ordnernamenmuster erwähnt. Bitte vermeiden Sie solche Antworten
Kunal Parekh