2008-10-29 12 views
11

He estado tratando de codificar una secuencia de comandos Perl para sustituir texto en todos los archivos fuente de mi proyecto. Estoy en la necesidad de algo así como:¿Hay una manera simple de hacer la sustitución de texto a granel en su lugar?

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

Pero que analiza todos los archivos de un directorio recursivamente .

acabo comenzó una secuencia de comandos:

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. ¿Existe 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; Soy tengo 'em all = subversioned)

actualización: He intentado 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, estoy recibiendo errores muy extraños en Cygwin ...

+0

Probablemente debería tener en cuenta que está ejecutando Windows. –

Respuesta

13

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 especifica en la línea de comandos.

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 quiere.

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.

+0

¡Exactamente lo que necesitaba! – Seiti

2

usted podría utilizar find:

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

Esto muestra todos los nombres de archivos de forma recursiva, a continuación, xargs leerá su entrada estándar y ejecutar el resto de la línea de comando con los nombres de archivo agregados al final. Una cosa buena de xargs es que ejecutará la línea de comando más de una vez si la línea de comandos que construye se vuelve demasiado larga para ejecutarse de una vez.

Tenga en cuenta que no estoy seguro de si find entiende completamente todos los métodos de concha de selección de archivos, por lo que si lo anterior no funciona, entonces quizás prueba:

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

Al utilizar tuberías de este tipo, me gusta para construir la línea de comando y ejecutar cada parte individualmente antes de continuar, para asegurarse de que cada programa obtenga la información que desea. Entonces, podría ejecutar la pieza sin xargs primero para verificarlo.

Se me acaba de ocurrir que, aunque usted no lo haya dicho, probablemente esté en Windows debido a los sufijos de archivo que está buscando. En ese caso, la tubería anterior podría ejecutarse utilizando Cygwin. Es posible escribir una secuencia de comandos de Perl para hacer lo mismo, como ya lo hizo, pero tendrá que realizar la edición in situ usted mismo porque no puede aprovechar el interruptor -i en esa situación.

+0

Trató encontrar. -name '*. {cs, aspx, ascx}' sin suerte, pero la versión grep enumeró los archivos. ¡Bonito! Pero cuando ejecuto todos los comandos obtengo esto: xargs: perl: lista de argumentos demasiado larga – Seiti

+0

xargs también puede limitar el número de argumentos pasados ​​en cada línea de comando, si no puede determinar la longitud máxima de la línea de comando . Use la opción -L o -n para xargs dependiendo de la versión que sea (consulte la página de manual). –

+0

Si va a utilizar find & xargs, use -print0 y -0 para evitar problemas con los nombres de archivos con espacios. buscar -print0 ... | xargs -0 ... – Schwern

4

Cambio

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 ocupar varias líneas. Si el patrón puede abarcar líneas, tendrá que sorber el contenido del archivo. ("sorber" es un término Perl bastante común).

La mordisco no es realmente necesario, sólo ha picado líneas que no eran chomp ed demasiadas veces (si se le cae la chomp, cambiar print $out "$line\n"; a print $out $line;).

Del mismo modo, puede cambiar open my $out, '>', "$f.out"; por open my $out, '>', undef; para abrir un archivo temporal y luego copiar el archivo de nuevo sobre el original cuando se realiza la sustitución. De hecho, y especialmente si consume 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 que siempre escribo en un nuevo archivo y verifico el contenido.


Nota, que originalmente tenían una sentencia if en dicho código. Eso probablemente estuvo mal. Eso solo habría sido copiado a través de líneas que coincidían con la expresión regular "thisgoesout" (reemplazándola por "thisgoesin", por supuesto) mientras engullía silenciosamente el resto.

7

Usted puede estar interesado en File::Transaction::Atomic o File::Transaction

la sinopsis para F :: T :: Una ve muy similar con lo que estamos tratando de 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 ([email protected]) { 
     $ft->revert; 
     die "update aborted: [email protected]"; 
    } 
    else { 
     $ft->commit; 
    } 

Pareja que con el archivo :: Encuentra que ya has escrito, y deberías ser bueno para ir.

6

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

+0

¿Por qué apuntar a man (3perl) en lugar de Perldoc? – ephemient

+0

Sí, Tie :: El archivo fue creado para este tipo de cosas. – Schwern

+0

http://perldoc.perl.org/Tie/File.html –

1

Gracias a ephemient sobre esta cuestión y sobre this answer, tengo 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 puedo incluso bucle a través de un hash que contienen entradas de expresiones regulares => subs!

+0

Probablemente debería 'local'ize' @ ARGV' y '$^I' dentro de esta rutina, ya que estas variables tienen efectos bastante globales. – ephemient

Cuestiones relacionadas