2008-10-02 12 views
5

A volte si sente dire su Perl che ci potrebbe essere 6 modi diversi per affrontare lo stesso problema. I buoni sviluppatori di Perl di solito hanno idee ben ragionate per fare delle scelte tra i vari possibili metodi di implementazione.Perl Challenge - Directory Iterator

Così un esempio Perl problema:

Un semplice script che in modo ricorsivo scorre una struttura di directory, alla ricerca di file che sono stati modificati di recente (dopo una certa data, che sarebbe variabile). Salva i risultati in un file.

La domanda, per gli sviluppatori di Perl: Qual è il tuo modo migliore per ottenere questo risultato?

+0

Penso che potresti avere un problema con la semplicità, spesso in Perl la semplicità non è la soluzione migliore o più elegante. –

+0

Buon punto, grazie. Lascerò a tutti la possibilità di farlo nelle loro risposte se ritengono che ciò contribuisca alla loro soluzione. – keparo

risposta

17

Questo suona come un lavoro per 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

Nice - anche se ha lo svantaggio di non essere un modulo standard. – slim

+3

Non esiste un modulo "standard". Se intendi modulo in bundle con perl stesso, questi rientrano in due categorie: storico o di utilizzo nell'installazione di altri moduli; nessuno dei due è un buon motivo per preferire quelli a qualcos'altro da CPAN. – ysth

-1

Scrivo una subroutine che legge una directory con readdir, genera "." e ".." directory, recurses se trova una nuova directory, ed esamina il file per quello che sto cercando (nel tuo caso, ti consigliamo di utilizzare utime o stat). A tempo debito la ricorsione è fatta, ogni file dovrebbe essere stato esaminato.

Credo che tutte le funzioni che ci si bisogno per questo script sono descritti brevemente: http://www.cs.cf.ac.uk/Dave/PERL/node70.html

La semantica di ingresso e di uscita sono un esercizio abbastanza banale che lascio a voi.

+0

Preghiamo solo che tu non abbia un link simbolico che rimandi ad una directory degli antenati, altrimenti questo semplice approccio andrà per sempre. – dland

+0

Di solito script in Windows, dove questo non è un problema. In Linux, è possibile scrivere assegni per verificare la presenza di collegamenti simbolici per evitare il problema, se necessario. Creerebbe comunque un problema grazie così per averlo indicato! Grazie per aver menzionato la semplicità della mia risposta: era quello che è stato chiesto ... – antik

-2

Sono rischioso di ottenere downvoted, ma il comando IMHO 'ls' (con parametri appropriati) lo fa in un modo performante noto. In questo caso potrebbe essere una buona soluzione passare pipe 'ls' dal codice perl tramite shell, restituendo i risultati su un array o un hash.

Edit: Potrebbe anche essere 'trovare' utilizzato, come proposto nei commenti.

+0

non molto buono se lo script non viene utilizzato su un sistema operativo basato su * nix. – workmad3

+1

ls non può effettuare selezioni complesse. Inoltre non gestisce le newline incorporate nei nomi dei file. –

+0

Se è necessario abbandonare la portabilità e chiamare un comando di shell, "trova" è quello che corrisponde alle esigenze del questionario. Tuttavia, File :: Trova ottiene la stessa cosa in Perl nativo ed è preferibile. – slim

8

File::Find è il modo giusto per risolvere questo problema. Non c'è alcun uso nel reimplementare cose che già esistono in altri moduli, ma il reimplementing di qualcosa che si trova in un modulo standard dovrebbe essere scoraggiato.

+0

File :: Find non fa le cose nel modo migliore possibile.Ignora qualsiasi ritorno da "ricercato", quindi non puoi impedirgli di attraversare parti dell'albero di cui non hai bisogno, se ciò fosse possibile, perché raccoglie tutti i percorsi per primo e li restituisce a tutti in una volta. – Axeman

+1

File :: Trova non restituisce alcun percorso, effettua una chiamata alla funzione desiderata. Potresti "morire()" nella funzione desiderata e intrappolarlo con un "eval {}" attorno a find() se volevi un'uscita anticipata. File :: Trova :: La regola restituisce tutti i percorsi. – runrig

+0

Sopra commento era in risposta a Axeman. Inoltre, è File :: Trova :: Regola che raccoglie tutti i percorsi e li restituisce tutti in una volta e non può uscire in anticipo. – runrig

4

Il mio metodo preferito è quello di utilizzare il File :: Find modulo come così:

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

Se il problema è risolto principalmente da librerie standard li usano.

File :: Find in questo caso funziona bene.

Ci possono essere molti modi per fare le cose in perl, ma dove esiste una libreria molto standard per fare qualcosa, dovrebbe essere utilizzata a meno che non abbia problemi di sua proprietà.

#!/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

Non è necessario ottenere l'ora corrente e convertire i giorni in secondi, ($ giorni <= -M) farebbe – runrig

+0

Oppure ($ giorni> = -M), ora che ho letto l'OP. – runrig

9

Non ci sono sei modi per fare questo, c'è il vecchio modo, e il nuovo modo. Il vecchio modo è con File :: Trova, e ne hai già un paio di esempi. File :: Find ha un'interfaccia di callback piuttosto orribile, è stato bello vent'anni fa, ma da allora siamo passati.

Ecco un programma di vita reale (leggermente modificato) che utilizzo per eliminare il cruft su uno dei miei server di produzione. Usa File :: Trova :: Regola, piuttosto che File :: Trova. File :: Trova :: regola ha una bella interfaccia dichiarativa che legge facilmente.

Randal Schwartz ha anche scritto File :: Finder, come wrapper su File :: Trova. È abbastanza bello ma non è davvero decollato.

#! /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

Altri hanno menzionato File :: Find, che è il modo in cui mi piacerebbe andare, ma è chiesto un iteratore, che File :: Find non è (né è File :: Find :: Rule) . Si potrebbe voler guardare File::Next o File::Find::Object, che hanno un'interfaccia iterativa. Mark Jason Dominus ripensa alla costruzione del capitolo 4.2.2 di Higher Order Perl.

+1

E per essere onesti con MJD, File :: Next viene direttamente estratto dal suo libro. –

3

Ho scritto File::Find::Closures come un insieme di chiusure che è possibile utilizzare con File :: Trova in modo da non dover scrivere il proprio. Ci sono un paio di funzioni mtime che dovrebbe gestire

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

Non avete davvero bisogno di usare il modulo perché in gran parte progettato come un modo che si potrebbe guardare il codice e rubare le parti che si voleva. In questo caso è un po 'più complicato perché tutte le subroutine che gestiscono le statistiche dipendono da una seconda subroutine. Avrai presto l'idea dal codice.

Buona fortuna,

0

L'utilizzo di moduli standard è davvero una buona idea, ma di interesse qui è il mio ritorno all'approccio di base che non utilizza moduli esterni. So che la sintassi del codice qui potrebbe non essere la tazza di tè di tutti.

Potrebbe essere migliorato utilizzare meno memoria fornendo un accesso iteratore (l'elenco di input potrebbe essere temporaneamente in attesa una volta raggiunta una certa dimensione) e il controllo condizionale può essere espanso tramite richiamata rif.

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