sed principiante: modifica di tutte le occorrenze in una cartella


98

Devo fare una regex trova e sostituisci tutti i file in una cartella (e le sue sottocartelle). Quale sarebbe il comando della shell di Linux per farlo?

Ad esempio, voglio eseguirlo su tutti i file e sovrascrivere il vecchio file con il nuovo testo sostituito.

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

Risposte:


148

Non c'è modo di farlo usando solo sed. Dovrai usare almeno l'utilità di ricerca insieme:

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

Questo comando creerà un .bakfile per ogni file modificato.

Appunti:

  • L' -iargomento per il sedcomando è un'estensione GNU, quindi, se stai eseguendo questo comando con i BSD, seddovrai reindirizzare l'output a un nuovo file e rinominarlo.
  • L' findutility non implementa l' -execargomento nei vecchi box UNIX, quindi, al suo | xargsposto , dovrai usare un .

4
A cosa serve \;?
Andriy Makukha

4
Dobbiamo indicare per trovare dove il comando dell'argomento -exec finisce con ";". Ma la shell usa lo stesso simbolo (;) come separatore di comandi di shell, quindi, abbiamo bisogno di sfuggire al ";" dalla shell per passarlo all'argomento -exec di find.
osantana

2
Vale la pena notare che di -iper sé non crea un file di backup, ed è ciò che fa sì che sed esegua l'operazione sul file in posizione.
Kyle

1
A cosa serve {}?
somenickname

1
Il {}sarà sostituito da ogni nome di file trovato da finde \;dice di trovare che il comando che deve eseguire finisce a questo punto.
osantana

51

Io preferisco usare find | xargs cmdsopra find -execperché è più facile da ricordare.

Questo esempio sostituisce globalmente "foo" con "bar" nei file .txt nella directory corrente o sotto di essa:

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

Le opzioni -print0e -0possono essere omesse se i nomi dei file non contengono caratteri stravaganti come gli spazi.


3
Se sei su OSX, prova find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' "s/foo/bar/g"(nota fornendo una stringa vuota -iall'argomento).
Jakub Kukul

6

Per la portabilità, non mi affido a funzionalità di sed specifiche per Linux o BSD. Invece io uso lo overwritescript dal libro di Kernighan e Pike sull'ambiente di programmazione Unix.

Il comando è quindi

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

E lo overwritescript (che uso ovunque) lo è

#!/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'idea è che sovrascrive un file solo se un comando riesce. Utile in finde anche dove non vorresti usare

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

perché la shell tronca il file prima di sedpoterlo leggere.


3

Potrei suggerire (dopo aver eseguito il backup dei file):

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

0

Esempio: sostituire {AutoStart} con 1 per tutti i file ini nella cartella / app / config / e nelle relative cartelle secondarie:

sed 's/{AutoStart}/1/g' /app/config/**/*.ini

0
for i in $(ls);do sed -i 's/old_text/new_text/g' $i;done 

5
Per favore, spiega la tua risposta.
Ritiro il

Sebbene questo codice possa risolvere il problema dell'OP, è meglio includere una spiegazione su come il tuo codice risolve il problema dell'OP. In questo modo, i futuri visitatori possono imparare dal tuo post e applicarlo al proprio codice. SO non è un servizio di codifica, ma una risorsa per la conoscenza. Risposte complete e di alta qualità rafforzano questa idea e hanno maggiori probabilità di essere votate. Queste caratteristiche, oltre al requisito che tutti i post siano autonomi, sono alcuni punti di forza di SO come piattaforma che ci differenzia dai forum. Puoi modificare per aggiungere ulteriori informazioni e / o per integrare le tue spiegazioni con la documentazione di origine.
SherylHohman,

1
Se non puoi leggere questo, dimentica la mia risposta. È solo bash bash.
DimiDak

-1

Potrei provare la mia ricerca di massa / sostituire lo script Perl . Presenta alcuni vantaggi rispetto alle soluzioni di utilità concatenate (come non dover gestire più livelli di interpretazione dei metacaratteri della 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";
}

-3

Nel caso in cui il nome dei file nella cartella abbia alcuni nomi regolari (come file1, file2 ...) ho usato per il ciclo.

for i in {1..10000..100}; do sed 'old\new\g' 'file'$i.xml > 'cfile'$i.xml; done

questo non è correlato alla domanda posta. La domanda non menziona nulla sullo stesso modello di nome di file / cartella. Si prega di evitare tali risposte
Kunal Parekh
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.