Pregunta

A veces escuchas que Perl dice que podría haber 6 formas diferentes de abordar el mismo problema. Los buenos desarrolladores de Perl por lo general tienen ideas bien razonadas para elegir entre los diversos métodos posibles de implementación.

Un ejemplo de problema de Perl:

Un script simple que recorre iterativamente a través de una estructura de directorios, buscando archivos que se modificaron recientemente (después de una fecha determinada, que serían variables). Guarda los resultados en un archivo.

La pregunta, para los desarrolladores de Perl: ¿Cuál es tu mejor manera de lograr esto?

¿Fue útil?

Solución

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";

Otros consejos

Cuando el problema se resuelve principalmente mediante bibliotecas estándar, utilícelos.

Archivo :: Buscar en este caso funciona bien.

Puede haber muchas formas de hacer cosas en perl, pero donde 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(

Cuando el problema se resuelve principalmente mediante bibliotecas estándar, utilícelos.

Archivo :: Buscar en este caso funciona bien.

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

<*>); if (($time - $stat[9]) >= $days) { print "

Cuando el problema se resuelve principalmente mediante bibliotecas estándar, utilícelos.

Archivo :: Buscar en este caso funciona bien.

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

<*> \n"; } }

No hay seis formas de hacer esto, hay la forma antigua y la forma nueva. 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 continuado desde entonces.

Aquí hay un programa de la vida real (ligeramente modificado) que utilizo para borrar el crucero 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 bueno, pero realmente 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);
    }
}

File :: Find Es la forma correcta de resolver este problema. No es útil reimplementar cosas que ya existen en otros módulos, pero reimplementar algo que se encuentra en un módulo estándar debería desalentarse.

Otros han mencionado File :: Find, que es el camino que seguiría, pero usted pidió un iterador, que File :: Find no es (ni File :: Find :: Rule). Es posible que desee ver File :: Next o File :: Find :: Object , que tienen interfaces iterativas. Mark Jason Dominus va a crear el suyo propio en el capítulo 4.2.2 de Perl de orden superior .

Mi método preferido es usar el módulo Archivo :: Buscar de la siguiente manera:

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

sub checkFile()
{
   #examine each file in here. Filename is in 

Mi método preferido es usar el módulo Archivo :: Buscar de la siguiente manera:

<*> and you are chdired into it's directory #directory is also available in $File::Find::dir }

Hay mi File :: Finder , como ya se mencionó, pero también hay mi solución iterador-como-un-hash atada de Búsqueda de archivos de forma incremental (Linux Magazine) .

Escribí File :: Find :: Closures como un conjunto de Cierres que puede usar con File :: Find para que no tenga que escribir el suyo propio. Hay un par de funciones mtime que deberían manejarse

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->();

No es necesario que uses el módulo porque lo diseñé principalmente para mirar el código y robar las partes que querías. En este caso es un poco más complicado porque todas las subrutinas que tratan con estadísticas dependen de una segunda subrutina. Sin embargo, rápidamente obtendrás la idea del código.

Buena suerte,

El uso de módulos estándar es de hecho una buena idea, pero por interés aquí estoy de vuelta a un enfoque básico sin usar módulos externos. Sé que la sintaxis del código aquí puede no ser la taza de té de todos.

Se podría mejorar el uso de menos memoria al proporcionar un acceso de iterador (la lista de entrada podría estar temporalmente en espera una vez que alcance un cierto tamaño) y la verificación condicional se puede expandir a través de la referencia de devolución de llamada.

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{

El uso de módulos estándar es de hecho una buena idea, pero por interés aquí estoy de vuelta a un enfoque básico sin usar módulos externos. Sé que la sintaxis del código aquí puede no ser la taza de té de todos.

Se podría mejorar el uso de menos memoria al proporcionar un acceso de iterador (la lista de entrada podría estar temporalmente en espera una vez que alcance un cierto tamaño) y la verificación condicional se puede expandir a través de la referencia de devolución de llamada.

<*>} @_ ? ( 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");

Escribo una subrutina que lee un directorio con readdir , arroja el ". " y " .. " directorios, recurre si encuentra un nuevo directorio, y examina los archivos en busca de lo que estoy buscando (en su caso, querrá usar utime o stat ) . Al momento en que se realiza la recursión, cada archivo debería haber sido examinado.

Creo que todas las funciones que necesitarías para este script se describen brevemente aquí: http://www.cs.cf.ac.uk/Dave/ PERL / node70.html

La semántica de entrada y salida es un ejercicio bastante trivial que te dejaré.

Me arriesgo a que me den un voto negativo, pero el comando IMHO 'ls' (con los parámetros adecuados) lo hace de la manera más conocida posible. En este caso, podría ser una solución bastante buena para canalizar 'ls' desde el código perl a través del shell, devolviendo los resultados a una matriz o hash.

Editar: También se podría usar 'buscar', como se propone en los comentarios.

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