iniciante sed: alterando todas as ocorrências em uma pasta

98

Eu preciso fazer um regex localizar e substituir em todos os arquivos em uma pasta (e suas subpastas). Qual seria o comando do shell do linux para fazer isso?

Por exemplo, desejo executar isso em todos os arquivos e sobrescrever o arquivo antigo com o novo texto substituído.

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

Respostas:

148

Não há como fazer isso usando apenas o sed. Você precisará usar pelo menos o utilitário find junto:

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

Este comando criará um .bakarquivo para cada arquivo alterado.

Notas:

  • O -iargumento para o sedcomando é uma extensão GNU, então, se você estiver executando este comando com os BSDs, sedvocê precisará redirecionar a saída para um novo arquivo e renomeá-lo.
  • O findutilitário não implementa o -execargumento nas caixas UNIX antigas, portanto, você precisará usar um | xargs.
osantana
fonte
4
Para que serve \;?
Andriy Makukha
4
Precisamos dizer para encontrar onde o comando do argumento -exec termina com um ”;”. Mas o shell usa o mesmo símbolo (;) como um separador de comando do shell, então, precisamos escapar do ”;” do shell para passá-lo para o argumento -exec de find.
osantana
2
É importante notar que -ipor si só não cria um arquivo de backup, e é o que faz com que o sed execute a operação no arquivo no local.
Kyle
1
Para que serve {}?
algum apelido
1
O {}será substituído por cada nome de arquivo encontrado por finde \;informa para encontrar que o comando que ele precisa executar termina neste ponto.
osantana
51

Prefiro usar find | xargs cmdover find -execporque é mais fácil de lembrar.

Este exemplo substitui globalmente "foo" por "bar" em arquivos .txt no diretório atual ou abaixo dele:

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

As opções -print0e -0podem ser deixadas de fora se os nomes dos arquivos não contiverem caracteres engraçados, como espaços.

Dennis
fonte
3
Se você estiver no OSX, tente find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' "s/foo/bar/g"(observe fornecendo uma string vazia para o -iargumento).
Jakub Kukul
6

Para portabilidade, eu não confio em recursos do sed que são específicos para linux ou BSD. Em vez disso, uso o overwritescript do livro de Kernighan e Pike sobre o ambiente de programação Unix.

O comando é então

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

E o overwritescript (que uso em todo lugar) é

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

A ideia é que ele substitua um arquivo apenas se um comando for bem-sucedido. Útil em finde também onde você não gostaria de usar

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

porque o shell trunca o arquivo antes de sedpoder lê-lo.

Norman Ramsey
fonte
3

Posso sugerir (depois de fazer backup de seus arquivos):

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

Exemplo: replase {AutoStart} com 1 para todos os arquivos ini na pasta / app / config / e suas pastas filhas:

sed 's/{AutoStart}/1/g' /app/config/**/*.ini
Alan Hu
fonte
0
for i in $(ls);do sed -i 's/old_text/new_text/g' $i;done 
DimiDak
fonte
5
Explique sua resposta.
Desistência em
Embora este código possa resolver o problema do OP, é melhor incluir uma explicação sobre como seu código aborda o problema do OP. Dessa forma, os futuros visitantes podem aprender com sua postagem e aplicá-la ao seu próprio código. SO não é um serviço de codificação, mas um recurso de conhecimento. Respostas completas de alta qualidade reforçam essa ideia e têm maior probabilidade de serem votadas a favor. Esses recursos, além do requisito de que todas as postagens sejam independentes, são alguns pontos fortes do SO como uma plataforma que nos diferencia dos fóruns. Você pode editar para adicionar informações adicionais e / ou complementar suas explicações com a documentação de origem.
SherylHohman
1
Se você não consegue ler isso, apenas esqueça minha resposta. É apenas o básico do bash.
DimiDak
-1

Pode querer tentar minha busca em massa / substituir script Perl . Tem algumas vantagens sobre as soluções de utilitário encadeado (como não ter que lidar com vários níveis de interpretação de metacaracteres 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";
}
caos
fonte
-3

Caso o nome dos arquivos na pasta tenha alguns nomes regulares (como arquivo1, arquivo2 ...) eu usei para o ciclo.

for i in {1..10000..100}; do sed 'old\new\g' 'file'$i.xml > 'cfile'$i.xml; done
Tereza
fonte
isso não está relacionado à pergunta feita. A pergunta não menciona nada sobre o mesmo padrão de nome de arquivo / pasta. Evite essas respostas
Kunal Parekh