principiante sed: cambiar todas las ocurrencias en una carpeta

98

Necesito buscar y reemplazar expresiones regulares en todos los archivos de una carpeta (y sus subcarpetas). ¿Cuál sería el comando de shell de Linux para hacer eso?

Por ejemplo, quiero ejecutar esto en todos los archivos y sobrescribir el archivo antiguo con el texto nuevo reemplazado.

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

Respuestas:

148

No hay forma de hacerlo usando solo sed. Deberá usar al menos la utilidad de búsqueda juntos:

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

Este comando creará un .bakarchivo para cada archivo modificado.

Notas:

  • El -iargumento para el sedcomando es una extensión GNU, por lo tanto, si está ejecutando este comando con los BSD, seddeberá redirigir la salida a un nuevo archivo y luego cambiarle el nombre.
  • La findutilidad no implementa el -execargumento en las cajas de UNIX antiguas, por lo que necesitará usar un | xargs.
Osantana
fuente
4
Lo que es \;para?
Andriy Makukha
4
Necesitamos decir para encontrar dónde termina el comando del argumento -exec con un ”;”. Pero el shell usa el mismo símbolo (;) como separador de comandos del shell, por lo que debemos escapar del ";" desde el shell para pasarlo al argumento -exec de find.
Osantana
2
Vale la pena señalar que -ipor sí solo no crea un archivo de respaldo, y es lo que hace que sed realice la operación en el archivo en su lugar.
Kyle
1
Lo que es {}para?
somenickname
1
El {}será reemplazado por cada nombre de archivo encontrado por findy \;le indica que el comando que necesita ejecutar termina en este punto.
Osantana
51

Prefiero usar find | xargs cmdover find -execporque es más fácil de recordar.

Este ejemplo reemplaza globalmente "foo" con "bar" en archivos .txt en o debajo de su directorio actual:

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

Las opciones -print0y -0se pueden omitir si sus nombres de archivo no contienen caracteres extravagantes como espacios.

Dennis
fuente
3
Si está en OSX, intente find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' "s/foo/bar/g"(tenga en cuenta que proporciona una cadena vacía al -iargumento).
Jakub Kukul
6

Para la portabilidad, no confío en características de sed que sean específicas de Linux o BSD. En su lugar, utilizo el overwritescript del libro de Kernighan y Pike sobre el entorno de programación Unix.

El comando es entonces

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

Y el overwriteguión (que utilizo en todas partes) es

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

La idea es que sobrescriba un archivo solo si un comando tiene éxito. Útil en findy también donde no querrías usar

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

porque el shell trunca el archivo antes de sedpoder leerlo.

Norman Ramsey
fuente
3

¿Puedo sugerir (después de hacer una copia de seguridad de sus archivos):

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

Ejemplo: reemplace {AutoStart} con 1 para todos los archivos ini en la carpeta / app / config / y sus carpetas secundarias:

sed 's/{AutoStart}/1/g' /app/config/**/*.ini
Alan Hu
fuente
0
for i in $(ls);do sed -i 's/old_text/new_text/g' $i;done 
DimiDak
fuente
5
Por favor explique su respuesta.
Abandono el
Si bien este código puede resolver el problema del OP, es mejor incluir una explicación sobre cómo su código aborda el problema del OP. De esta manera, los futuros visitantes pueden aprender de su publicación y aplicarla a su propio código. SO no es un servicio de codificación, sino un recurso de conocimiento. Las respuestas completas y de alta calidad refuerzan esta idea y es más probable que se voten a favor. Estas características, más el requisito de que todas las publicaciones sean independientes, son algunas de las fortalezas de SO como plataforma que nos diferencia de los foros. Puede editar para agregar información adicional y / o complementar sus explicaciones con documentación fuente.
SherylHohman
1
Si no puede leer esto, simplemente olvide mi respuesta. Es solo lo básico de bash.
DimiDak
-1

Es posible que desee probar mi secuencia de comandos Perl de búsqueda / reemplazo masivo . Tiene algunas ventajas sobre las soluciones de utilidad encadenada (como no tener que lidiar con múltiples niveles de interpretación de metacaracteres de 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
fuente
-3

En caso de que el nombre de los archivos en la carpeta tenga algunos nombres regulares (como archivo1, archivo2 ...) que he usado para el ciclo.

for i in {1..10000..100}; do sed 'old\new\g' 'file'$i.xml > 'cfile'$i.xml; done
tereza
fuente
esto no está relacionado con la pregunta formulada. La pregunta no menciona nada sobre el mismo patrón de nombre de archivo / carpeta. Evite tales respuestas
Kunal Parekh