Pregunta

He estado intentando codificar un script de Perl para sustituir texto en todos los archivos fuente de mi proyecto. Necesito algo como:

perl -p -i.bak -e "s/thisgoesout/thisgoesin/gi" *.{cs,aspx,ascx}

Pero eso analiza todos los archivos de un directorio recursivamente .

Acabo de comenzar un script:

use File::Find::Rule;
use strict;

my @files = (File::Find::Rule->file()->name('*.cs','*.aspx','*.ascx')->in('.'));

foreach my $f (@files){
    if ($f =~ s/thisgoesout/thisgoesin/gi) {
           # In-place file editing, or something like that
    }
}

Pero ahora estoy atascado. ¿Hay una manera simple de editar todos los archivos en su lugar usando Perl?

Tenga en cuenta que no necesito guardar una copia de cada archivo modificado; Los tengo todos subversionados =)

Actualización : Probé esto en Cygwin ,

perl -p -i.bak -e "s/thisgoesout/thisgoesin/gi" {*,*/*,*/*/*}.{cs,aspx,ascx

Pero parece que mi lista de argumentos explotó al tamaño máximo permitido. De hecho, recibo errores muy extraños en Cygwin ...

¿Fue útil?

Solución

Si asigna @ARGV antes de usar *ARGV (también conocido como el diamante <>), $^I / -i funcionará en esos archivos en lugar de lo que se especificó en la línea de comando.

use File::Find::Rule;
use strict;

@ARGV = (File::Find::Rule->file()->name('*.cs', '*.aspx', '*.ascx')->in('.'));
$^I = '.bak';  # or set `-i` in the #! line or on the command-line

while (<>) {
    s/thisgoesout/thisgoesin/gi;
    print;
}

Esto debería hacer exactamente lo que quieres.

Si su patrón puede abarcar varias líneas, agregue un undef $/; antes del <=> para que Perl opere en un archivo completo a la vez en lugar de línea por línea.

Otros consejos

Puede interesarle Archivo :: Transacción :: Atomic o Archivo :: Transacción

La SINOPSIS para F :: T :: A se ve muy similar a lo que intenta hacer:

  # In this example, we wish to replace 
  # the word 'foo' with the word 'bar' in several files, 
  # with no risk of ending up with the replacement done 
  # in some files but not in others.

  use File::Transaction::Atomic;

  my $ft = File::Transaction::Atomic->new;

  eval {
      foreach my $file (@list_of_file_names) {
          $ft->linewise_rewrite($file, sub {
               s#\bfoo\b#bar#g;
          });
      }
  };

  if ($@) {
      $ft->revert;
      die "update aborted: $@";
  }
  else {
      $ft->commit;
  }

Combina eso con el Archivo :: Encuentra que ya has escrito, y deberías estar listo.

Puede usar Tie :: File para acceder de forma escalable a archivos grandes y cambiarlos en su lugar. Vea la página de manual (man 3perl Tie :: File).

Cambiar

foreach my $f (@files){
    if ($f =~ s/thisgoesout/thisgoesin/gi) {
           #inplace file editing, or something like that
    }
}

Para

foreach my $f (@files){
    open my $in, '<', $f;
    open my $out, '>', "$f.out";
    while (my $line = <$in>){
        chomp $line;
        $line =~ s/thisgoesout/thisgoesin/gi
        print $out "$line\n";
    }
}

Esto supone que el patrón no abarca varias líneas. Si el patrón puede abarcar líneas, necesitará sorber el contenido del archivo. (" sorber " es un término Perl bastante común).

El chomp no es realmente necesario, solo he sido mordido por líneas que no fueron chomp editadas demasiadas veces (si suelta print $out "$line\n";, cambie print $out $line; a open my $out, '>', "$f.out";).

Del mismo modo, puede cambiar open my $out, '>', undef; a <=> para abrir un archivo temporal y luego copiar ese archivo nuevamente sobre el original cuando la sustitución haya finalizado. De hecho, y especialmente si extrae todo el archivo, simplemente puede hacer la sustitución en la memoria y luego escribir sobre el archivo original. Pero he cometido suficientes errores al hacer eso y siempre escribo en un nuevo archivo y verifico el contenido.


Nota , originalmente tenía una declaración if en ese código. Eso probablemente estaba mal. Eso solo habría copiado sobre líneas que coinciden con la expresión regular & Quot; thisgoesout & Quot; (reemplazándolo con & "; esto va &"; por supuesto) mientras engulle silenciosamente el resto.

Puede usar find:

find . -name '*.{cs,aspx,ascx}' | xargs perl -p -i.bak -e "s/thisgoesout/thisgoesin/gi"

Esto enumerará todos los nombres de archivo de forma recursiva, luego xargs leerá su stdin y ejecutará el resto de la línea de comando con los nombres de archivo agregados al final. Una cosa buena de -i es que ejecutará la línea de comando más de una vez si la línea de comando que construye es demasiado larga para ejecutarse de una vez.

Tenga en cuenta que no estoy seguro de si <=> comprende completamente todos los métodos de shell para seleccionar archivos, así que si lo anterior no funciona, entonces quizás intente:

find . | grep -E '(cs|aspx|ascx)$' | xargs ...

Cuando uso tuberías como esta, me gusta construir la línea de comando y ejecutar cada parte individualmente antes de continuar, para asegurarme de que cada programa obtenga la entrada que desea. Por lo tanto, puede ejecutar la pieza sin <=> primero comprobarla.

Se me ocurrió que, aunque no lo dijiste, probablemente estés en Windows debido a los sufijos de archivos que estás buscando. En ese caso, la tubería anterior podría ejecutarse utilizando Cygwin. Es posible escribir un script de Perl para hacer lo mismo, como comenzó a hacer, pero tendrá que hacer la edición en el lugar usted mismo porque no puede aprovechar el interruptor <=> en esa situación.

Gracias a Ephemient por esta pregunta y por esto respuesta , obtuve esto:

use File::Find::Rule;
use strict;

sub ReplaceText {
    my $regex = shift;
    my $replace = shift;

    @ARGV = (File::Find::Rule->file()->name('*.cs','*.aspx','*.ascx')->in('.'));
    $^I = '.bak';
    while (<>) {
        s/$regex/$replace->()/gie;
        print;
    }
}

ReplaceText qr/some(crazy)regexp/, sub { "some $1 text" };

¡Ahora incluso puedo recorrer un hash que contiene regexp = > subs entradas!

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top