sed początkujący: zmiana wszystkich wystąpień w folderze

98

Muszę znaleźć wyrażenie regularne i zamienić je na wszystkich plikach w folderze (i jego podfolderach). Jakie byłoby polecenie powłoki systemu Linux, aby to zrobić?

Na przykład chcę przeprowadzić to na wszystkich plikach i nadpisać stary plik nowym, zastąpionym tekstem.

sed 's/old text/new text/g' 
nickf
źródło

Odpowiedzi:

150

Nie da się tego zrobić używając tylko seda. Będziesz musiał użyć przynajmniej narzędzia wyszukiwania razem:

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

To polecenie utworzy .bakplik dla każdego zmienionego pliku.

Uwagi:

  • -iArgument za sedpolecenie jest rozszerzeniem GNU, więc, jeśli używasz tego polecenia z BSD to sedtrzeba będzie przekierować wyjście do nowego pliku, a następnie zmień jego nazwę.
  • findNarzędzie nie wdrożyć -execargument w starych skrzynek UNIX, więc trzeba będzie użyć | xargszamiast.
osantana
źródło
4
Do czego służy \;?
Andriy Makukha
4
Musimy powiedzieć, aby znaleźć miejsce, w którym polecenie argumentu -exec kończy się na ”;”. Ale powłoka używa tego samego symbolu (;) jako separatora poleceń powłoki, więc musimy uciec przed ”;” z powłoki, aby przekazać ją do argumentu find -exec.
osantana
2
Warto zauważyć, że -isam w sobie nie tworzy pliku kopii zapasowej i to powoduje, że sed wykonuje operację na pliku w miejscu.
Kyle
1
Do czego służy {}?
somenickname
1
{}Zostaną zastąpione przez każdego pliku znalezionego przez findi \;mówi się że komenda, że musi wykonać wykończenie w tym momencie.
osantana
53

Wolę używać find | xargs cmdwięcej, find -execponieważ jest łatwiejszy do zapamiętania.

Ten przykład globalnie zamienia „foo” na „bar” w plikach .txt w bieżącym katalogu lub poniżej:

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

-print0I -0opcje mogą być pominięte, jeśli nazwy plików nie zawierają znaki funkowe, takich jak przestrzenie.

Dennis
źródło
3
Jeśli używasz OSX, spróbuj find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' "s/foo/bar/g"(pamiętaj o podaniu pustego ciągu do -iargumentu).
Jakub Kukul
W systemie MacOS uruchom sed -i.bakzamiast sed -i. Myślę, że jak wspomniał @JakubKukul, sed -i ''również działa.
forzagreen
7

Jeśli chodzi o przenośność, nie polegam na funkcjach seda, które są specyficzne dla Linuksa lub BSD. Zamiast tego używam overwriteskryptu z książki Kernighan i Pike o środowisku programowania Unix.

Rozkaz jest wtedy

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

A overwriteskrypt (którego używam wszędzie) to

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

Chodzi o to, że nadpisuje plik tylko wtedy, gdy polecenie się powiedzie. Przydatne w, finda także tam, gdzie nie chcesz używać

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

ponieważ powłoka obcina plik, zanim będzie sedmógł go odczytać.

Norman Ramsey
źródło
3

Mogę zasugerować (po utworzeniu kopii zapasowej plików):

find /the/folder -type f -exec sed -ibak 's/old/new/g' {} ';'
paxdiablo
źródło
0

Przykład: zastąp {AutoStart} wartością 1 dla wszystkich plików ini w folderze / app / config / i jego folderach podrzędnych:

sed 's/{AutoStart}/1/g' /app/config/**/*.ini
Alan Hu
źródło
0
for i in $(ls);do sed -i 's/old_text/new_text/g' $i;done 
DimiDak
źródło
6
Proszę wyjaśnij swoją odpowiedź.
Rezygnacja
1
Chociaż ten kod może rozwiązać problem z PO, lepiej jest dołączyć wyjaśnienie, w jaki sposób Twój kod rozwiązuje problem OP. W ten sposób przyszli odwiedzający mogą uczyć się z Twojego posta i zastosować go do własnego kodu. SO nie jest usługą programistyczną, ale źródłem wiedzy. Wysokiej jakości, kompletne odpowiedzi wzmacniają ten pomysł i są bardziej skłonne do głosowania. Te cechy, plus wymóg, aby wszystkie posty były niezależne, są mocnymi stronami SO jako platformy, która odróżnia nas od forów. Możesz edytować, aby dodać dodatkowe informacje i / lub uzupełnić wyjaśnienia o dokumentację źródłową.
SherylHohman
2
Jeśli nie możesz tego przeczytać, zapomnij o mojej odpowiedzi. To tylko podstawy.
DimiDak
0

To zadziałało dla mnie (na terminalu Mac, w systemie Linux, którego nie potrzebujesz '' -e):

sed -i '' -e 's/old text/new text/g' `grep 'old text' -rl *`

polecenie grep 'old text' -rl *wyświetla wszystkie pliki w katalogu roboczym (i podkatalogach), w których istnieje „stary tekst”. To jest następnie przekazywane w sed.

Foivos
źródło
-1

Może zechcesz wypróbować mój masowy skrypt wyszukiwania / zamiany Perla . Ma pewne zalety w porównaniu z rozwiązaniami o charakterze łańcuchowym (np. Brak konieczności radzenia sobie z wieloma poziomami interpretacji metaznaków powłoki).

#!/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
źródło
-3

W przypadku, gdy nazwa plików w folderze ma jakieś zwykłe nazwy (takie jak plik1, plik2 ...) użyłem dla cyklu.

for i in {1..10000..100}; do sed 'old\new\g' 'file'$i.xml > 'cfile'$i.xml; done
tereza
źródło
nie ma to związku z zadanym pytaniem. Pytanie nie wspomina nic o tym samym wzorze nazwy pliku / folderu. Proszę unikać takich odpowiedzi
Kunal Parekh