2008-10-02 7 views
5

A veces escuchas decir acerca de Perl que podría haber 6 formas diferentes de abordar el mismo problema. Los desarrolladores de Good Perl suelen tener conocimientos bien razonados para elegir entre los diversos métodos posibles de implementación.Perl Challenge - Iterador de directorio

Así un ejemplo Perl problema:

Una secuencia de comandos simple que forma recursiva itera a través de una estructura de directorios, en busca de archivos que se modificaron recientemente (después de una fecha determinada, lo que sería la variable). Guarde los resultados en un archivo.

La pregunta para los desarrolladores de Perl: ¿Cuál es su mejor forma de lograr esto?

+0

Creo que puede tener un problema con la simplicidad, a menudo en la simplicidad perl no es la mejor o más elegante solución. –

+0

Buen punto, gracias. Dejaré que todo el mundo lo resuelva en sus respuestas si sienten que se agrega a su solución. – keparo

Respuesta

17

Esto suena como un trabajo para File::Find::Rule:

#!/usr/bin/perl 
use strict; 
use warnings; 
use autodie; # Causes built-ins like open to succeed or die. 
       # You can 'use Fatal qw(open)' if autodie is not installed. 

use File::Find::Rule; 
use Getopt::Std; 

use constant SECONDS_IN_DAY => 24 * 60 * 60; 

our %option = (
    m => 1,  # -m switch: days ago modified, defaults to 1 
    o => undef, # -o switch: output file, defaults to STDOUT 
); 

getopts('m:o:', \%option); 

# If we haven't been given directories to search, default to the 
# current working directory. 

if (not @ARGV) { 
    @ARGV = ('.'); 
} 

print STDERR "Finding files changed in the last $option{m} day(s)\n"; 


# Convert our time in days into a timestamp in seconds from the epoch. 
my $last_modified_timestamp = time() - SECONDS_IN_DAY * $option{m}; 

# Now find all the regular files, which have been modified in the last 
# $option{m} days, looking in all the locations specified in 
# @ARGV (our remaining command line arguments). 

my @files = File::Find::Rule->file() 
          ->mtime(">= $last_modified_timestamp") 
          ->in(@ARGV); 

# $out_fh will store the filehandle where we send the file list. 
# It defaults to STDOUT. 

my $out_fh = \*STDOUT; 

if ($option{o}) { 
    open($out_fh, '>', $option{o}); 
} 

# Print our results. 

print {$out_fh} join("\n", @files), "\n"; 
+0

Bueno, aunque tiene la desventaja de no ser un módulo estándar. – slim

+3

No existe el módulo "estándar". Si se refiere al módulo incluido con Perl, estos pertenecen a dos categorías: históricos, o de uso en la instalación de otros módulos; ninguno de los cuales es una buena razón para preferir esos a otra cosa de CPAN. – ysth

-1

Escribo una subrutina que lee un directorio con readdir, arroja el "." y directorios "..", recurre si encuentra un nuevo directorio y examina los archivos de lo que estoy buscando (en su caso, querrá usar utime o stat). Para cuando se complete la recursión, todos los archivos deberían haber sido examinados.

Creo que todas las funciones que usted necesita para este script se describen brevemente a continuación: http://www.cs.cf.ac.uk/Dave/PERL/node70.html

La semántica de entrada y salida son un ejercicio bastante trivial en el que voy a dejar a usted.

+0

Solo reza para que no tengas un enlace simbólico que apunte a un directorio de antepasados, de lo contrario, este enfoque simple se repetirá para siempre. – dland

+0

Normalmente script en Windows donde esto no es un problema. En Linux, se pueden escribir cheques para buscar enlaces simbólicos para evitar el problema, si es necesario. Sin embargo, crearía un problema, ¡así que gracias por señalarlo! Gracias por mencionar la simplicidad de mi respuesta: eso fue lo que se solicitó ... – antik

-2

Me arriesgo a obtener downvoted, pero en mi humilde opinión 'ls' (con los parámetros adecuados) lo hace de la mejor manera conocida. En este caso, podría ser una solución bastante buena para conectar 'ls' desde el código perl a través del shell, devolviendo los resultados a un array o hash.

Editar: También podría ser 'encontrado' utilizado, como se propone en los comentarios.

+0

no es muy bueno si el script no se usa en un sistema operativo basado en * nix. – workmad3

+1

ls no puede hacer selecciones complejas. Tampoco maneja las nuevas líneas incrustadas en los nombres de los archivos. –

+0

Si debe abandonar la portabilidad y llamar a un comando de shell, entonces 'encontrar' es el que coincide con las necesidades del interrogador. Sin embargo, File :: Find logra lo mismo en Perl nativo, y es preferible. – slim

8

File::Find es la forma correcta de resolver este problema. No sirve de nada volver a implementar cosas que ya existen en otros módulos, pero realmente debería desalentarse la reimplantación de algo que está en un módulo estándar.

+0

File :: Find no hace las cosas de la mejor manera posible.Ignora cualquier devolución de "deseado", por lo que no puede evitar que atraviese partes del árbol que no necesita, si eso fuera posible, porque reúne todas las rutas primero y las devuelve todas a la vez. – Axeman

+1

File :: Find no devuelve ninguna ruta, hace una llamada a su función "deseada". Podrías "morir()" en la función deseada y atraparlo con un "eval {}" alrededor del find() si querías una salida anticipada. File :: Find :: Rule devuelve todas las rutas. – runrig

+0

El comentario anterior fue en respuesta a Axeman. Además, es File :: Find :: Rule que reúne todas las rutas y las devuelve todas a la vez, y no puede salir temprano. – runrig

4

Mi método preferido es usar el archivo :: Encuentra módulo como así:

use File::Find; 
find (\&checkFile, $directory_to_check_recursively); 

sub checkFile() 
{ 
    #examine each file in here. Filename is in $_ and you are chdired into it's directory 
    #directory is also available in $File::Find::dir 
} 
15

Cuando el problema se resuelve principalmente por las bibliotecas estándar de usarlos.

File :: Find en este caso funciona muy bien.

Puede haber muchas formas de hacer cosas en perl, pero cuando existe una biblioteca muy estándar para hacer algo, debe utilizarse a menos que tenga problemas propios.

#!/usr/bin/perl 

use strict; 
use File::Find(); 

File::Find::find({wanted => \&wanted}, "."); 

sub wanted { 
    my (@stat); 
    my ($time) = time(); 
    my ($days) = 5 * 60 * 60 * 24; 

    @stat = stat($_); 
    if (($time - $stat[9]) >= $days) { 
    print "$_ \n"; 
    } 
} 
+0

No es necesario obtener la hora actual y convertir días a segundos, ($ days <= -M) haría – runrig

+0

O ($ days> = -M), ahora que leí el OP. – runrig

9

No hay seis maneras de hacer esto, hay la manera antigua, y la nueva forma. La forma antigua es con File :: Find, y ya tienes un par de ejemplos de eso. File :: Find tiene una interfaz de devolución de llamada bastante horrible, fue genial hace 20 años, pero hemos avanzado desde entonces.

Aquí hay un programa de la vida real (ligeramente modificado) que utilizo para limpiar el cruft en uno de mis servidores de producción. Utiliza File :: Find :: Rule, en lugar de File :: Find. File :: Find :: Rule tiene una interfaz declarativa agradable que se lee fácilmente.

Randal Schwartz también escribió File :: Finder, como un contenedor sobre File :: Find. Es bastante agradable, pero en realidad no ha despegado.

#! /usr/bin/perl -w 

# delete temp files on agr1 

use strict; 
use File::Find::Rule; 
use File::Path 'rmtree'; 

for my $file (

    File::Find::Rule->new 
     ->mtime('<' . days_ago(2)) 
     ->name(qr/^CGItemp\d+$/) 
     ->file() 
     ->in('/tmp'), 

    File::Find::Rule->new 
     ->mtime('<' . days_ago(20)) 
     ->name(qr/^listener-\d{4}-\d{2}-\d{2}-\d{4}.log$/) 
     ->file() 
     ->maxdepth(1) 
     ->in('/usr/oracle/ora81/network/log'), 

    File::Find::Rule->new 
     ->mtime('<' . days_ago(10)) 
     ->name(qr/^batch[_-]\d{8}-\d{4}\.run\.txt$/) 
     ->file() 
     ->maxdepth(1) 
     ->in('/var/log/req'), 

    File::Find::Rule->new 
     ->mtime('<' . days_ago(20)) 
     ->or(
      File::Find::Rule->name(qr/^remove-\d{8}-\d{6}\.txt$/), 
      File::Find::Rule->name(qr/^insert-tp-\d{8}-\d{4}\.log$/), 
     ) 
     ->file() 
     ->maxdepth(1) 
     ->in('/home/agdata/import/logs'), 

    File::Find::Rule->new 
     ->mtime('<' . days_ago(90)) 
     ->or(
      File::Find::Rule->name(qr/^\d{8}-\d{6}\.txt$/), 
      File::Find::Rule->name(qr/^\d{8}-\d{4}\.report\.txt$/), 
     ) 
     ->file() 
     ->maxdepth(1) 
     ->in('/home/agdata/redo/log'), 

) { 
    if (unlink $file) { 
     print "ok $file\n"; 
    } 
    else { 
     print "fail $file: $!\n"; 
    } 
} 

{ 
    my $now; 
    sub days_ago { 
     # days as number of seconds 
     $now ||= time; 
     return $now - (86400 * shift); 
    } 
} 
8

Otros han mencionado File :: Find, que es la forma me gustaría ir, pero le ha pedido un iterador, que File :: Buscar no es (ni es File :: Buscar :: Regla) . Es posible que desee mirar File::Next o File::Find::Object, que sí tienen interfaces iterativas. Mark Jason Dominus repasa la construcción del suyo en el capítulo 4.2.2 de Higher Order Perl.

+1

Y para ser justos con MJD, File :: Next está directamente arrancado de su libro. –

3

Escribí File::Find::Closures como un conjunto de cierres que puede usar con File :: Find para que no tenga que escribir el suyo. Hay un par de funciones mtime que debe manejar

 
use File::Find; 
use File::Find::Closures qw(:all); 

my($wanted, $list_reporter) = find_by_modified_after(time - 86400); 
#my($wanted, $list_reporter) = find_by_modified_before(time - 86400); 

File::Find::find($wanted, @directories); 

my @modified = $list_reporter->(); 

En realidad, no es necesario utilizar el módulo, ya que en su mayoría diseñado como una manera de que usted podría mirar el código y robar las piezas que usted quería. En este caso, es un poco más complicado porque todas las subrutinas que se ocupan de la estadística dependen de una segunda subrutina. Sin embargo, obtendrá rápidamente la idea del código.

Buena suerte,

0

El uso de módulos estándar es de hecho una buena idea, pero de aquí no me interesa volver al enfoque básico sin utilizar módulos externos. Sé que la sintaxis del código aquí podría no ser la taza de té de todos.

Podría mejorarse para utilizar menos memoria al proporcionar acceso a un iterador (la lista de entrada podría estar temporalmente en espera una vez que alcanza un cierto tamaño) y la verificación condicional se puede expandir mediante la devolución de llamada ref.

sub mfind { 
    my %done; 

    sub find { 
     my $last_mod = shift; 
     my $path = shift; 

     #determine physical link if symlink 
     $path = readlink($path) || $path;   

     #return if already processed 
     return if $done{$path} > 1; 

     #mark path as processed 
     $done{$path}++; 

     #DFS recursion 
     return grep{$_} @_ 
       ? (find($last_mod, $path), find($last_mod, @_)) 
       : -d $path 
        ? find($last_mod, glob("$path/*")) 
         : -f $path && (stat($path))[9] >= $last_mod 
          ? $path : undef; 
    } 

    return find(@_); 
} 

print join "\n", mfind(time - 1 * 86400, "some path"); 
Cuestiones relacionadas