2011-03-30 25 views
6

estoy usando este comando para buscar y reemplazar una cadena con otra en el símbolo del sistema:búsqueda recursiva y reemplazar usind Perl en cmd (Windows)

perl -pi -i.bak -e "s/Mohan/Sitaram/g" ab.txt 

Esto reemplaza Mohan con Sitaram en el archivo ab.txt en el directorio actual.

Sin embargo, quiero reemplazar todas las apariciones de Mohan con Sitaram en todos los archivos .txt en todos los subdirectorios (recursivamente). El uso de *.txt en lugar de ab.txt no funciona. Las expresiones regulares funcionan de otra manera ya que he descargado los paquetes regex para Windows. No funciona solo para este comando que dice

E:\>perl -pi -e "s/Sitaram/Mohan/g" *.txt 
Can't open *.txt: Invalid argument. 

¿Hay alguna manera de arreglar esto? ¿Un comando diferente tal vez?

Respuesta

7

find . -name "*.txt" | xargs perl -p -i -e "s/Sitaram/Mohan/g"

find se utiliza para buscar todas las * .txt archivos de forma recursiva.

xargs se utiliza para construir y ejecutar líneas de comando desde la entrada estándar.

6

solución de Windows

En Windows, un comando se puede ejecutar para múltiples archivos usando el comando forfiles. La opción /s le dice que busque directorios recursivamente.

forfiles /s /m *.txt /c "perl -pi -e s/Sitaram/Mohan/g @path" 

Si se comienza la búsqueda desde distinto del directorio de trabajo actual se desea, el suministro /p path\to\start.

solución Unix

En Unix, hay un comando más genérico que forfiles llamado xargs, que pasa a líneas de su entrada estándar como argumentos a la orden dada. Los directorios se buscan recursivamente para los archivos .txt usando el comando find.

find . -name '*.txt' | xargs perl -pi -e 's/Sitaram/Mohan/g' 

independiente de la plataforma solución

También puede codificar tanto la búsqueda de archivos y la sustitución de cadenas en Perl. El módulo básico File::Find puede ayudar con eso. (Módulo principal = distribuida con el intérprete.)

perl -MFile::Find -e 'find(sub{…}, ".")' 

Sin embargo, el código Perl será más largo y no quiero pasar tiempo de escribirlo. Implemente el submarino utilizando información de la página de manual File::Find vinculada anteriormente. Debe probar si el nombre del archivo termina con .txt y no es un directorio, crea su copia de seguridad y reescribe el archivo original en la versión modificada de la copia de seguridad.

Las citas diferirán en Windows; quizás escribir la secuencia de comandos en un archivo sea la única solución sensata.

Problemas con enfoque original de OP

En shell Unix, patrones globales (por ejemplo,*.txt) se expanden por el shell, mientras que Windows cmd los deja intactos y los pasa directamente al programa que se invoca. Es su trabajo manejarlos. Perl no puede hacer eso obviamente.

El segundo problema es que incluso bajo Unix, globbing no funcionaría como se desea. *.txt son todos .txt archivos en el directorio actual, sin incluir los de los subdirectorios y sus subdirectorios ...

+0

En cuanto a la solución independiente de la plataforma ... Escribí [un fragmento de código con 'File :: Find'] (http://stackoverflow.com/a/24634420/2157640) ayer. Puede usar eso para tener una idea de cómo podría ser. – Palec

1

Si vas a molestar con Perl, ¿por qué no simplemente ir a por todas y escribir una (corta) programa de Perl para hacer esto ¿para ti?

De esta manera, no pasará entre el shell y su programa, y ​​tendrá algo que es más universal y puede ejecutarse en múltiples sistemas operativos.

#!/usr/bin/env perl <-- Not needed for Windows, but tradition rules 
use strict; 
use warnings; 
use feature qw(say); 
use autodie;   # Turns file operations into exception based programming 

use File::Find;  # Your friend 
use File::Copy;  # For the "move" command 

# You could use Getopt::Long, but let's go with this for now: 

# Usage = mungestrings.pl <from> <to> [<dir>] 
#   Default dir is current 
# 
my $from_string = shift; 
my $to_string = shift; 
my $directory = shift; 

$from_string = quotemeta $from_string; # If you don't want to use regular expressions 

$directory = "." if not defined $directory; 

# 
# Find the files you want to operate on 
# 
my @files; 
find(
    sub { 
     return unless -f;  # Files only 
     return unless /\.txt$/ # Name must end in ".txt" 
     push @files, $File::Find::name; 
    }, 
    $directory 
); 

# 
# Now let's go through those files and replace the contents 
# 

for my $file (@files) { 
    open my $input_fh, "<", $file; 
    open my $output_fh, ">" "$file.tmp"; 
    for my $line (<$input_fh>) { 
     $line =~ s/$from_string/$to_string/g; 
     print ${output_fh} $line; 
    } 

    # 
    # Contents been replaced move temp file over original 
    # 
    close $input_fh; 
    close $output_fh; 
    move "$file.tmp", $file; 
} 

utilizo File::Find para reunir todos los archivos que desea modificar en mi arsenal @files. Podría haber puesto todo el asunto dentro de la subrutina find:

find(\&wanted, $directory); 

sub wanted { 
    return unless -f; 
    return unless /\.txt/; 
    # 
    # Here: open the file for reading, open output and move the lines over 
    # 
    ... 
} 

El conjunto del programa se encuentra en la wanted subrutina de esta manera. Es más eficiente porque ahora estoy reemplazando ya que estoy buscando los archivos. No es necesario pasar primero, encontrar los archivos, luego hacer el reemplazo. Sin embargo, me parece un mal diseño.

También puede sorber todo su archivo en una matriz sin bucle a través de él al principio:

open my $input_fh, "<", $file; 
@input_file = <$input_fh>; 

Ahora, puede utilizar grep comprobar para ver si hay algo que necesita ser reemplazado:

if (grep { $from_string } @input_file) { 
    # Open an output file, and do the loop to replace the text 
} 
else { 
    # String not here. Just close up the input file 
    # and don't bother with writing a new one and moving it over 
} 

Esto es más eficiente (no es necesario reemplazar, a menos que ese archivo tenga la cadena que está buscando). Sin embargo, ocupa memoria (todo el archivo debe estar en la memoria al mismo tiempo), y no dejes que esa línea te engañe. El archivo completo aún se lee en esa matriz una línea a la vez como si hiciera un ciclo completo.

File::Find y son módulos estándar de Perl, por lo que todas las instalaciones de Perl los tienen.

Cuestiones relacionadas