Perl Challenge - итератор каталогов - PullRequest
5 голосов
/ 02 октября 2008

Вы иногда слышите, что в Perl сказано, что может быть 6 разных способов решения одной и той же проблемы. У хороших разработчиков Perl обычно есть веские аргументы в пользу выбора между различными возможными методами реализации.

Пример проблемы с Perl:

Простой скрипт, который рекурсивно перебирает структуру каталогов, ища файлы, которые были изменены недавно (после определенной даты, которая будет переменной). Сохранить результаты в файл.

Вопрос для разработчиков Perl: каков ваш лучший способ сделать это?

Ответы [ 11 ]

17 голосов
/ 02 октября 2008

Это звучит как работа для 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";
15 голосов
/ 02 октября 2008

Там, где проблему решают в основном стандартные библиотеки, их используют.

File :: Find в этом случае прекрасно работает.

Может быть много способов сделать что-то в Perl, но там, где существует очень стандартная библиотека для чего-то, ее следует использовать, если у нее нет собственных проблем.

#!/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";
  }
}
9 голосов
/ 02 октября 2008

Для этого нет шести способов, есть старый и новый. Старый способ с File :: Find, и у вас уже есть несколько примеров этого. File :: Find имеет довольно ужасный интерфейс обратного вызова, это было круто 20 лет назад, но с тех пор мы продвинулись дальше.

Вот реальная (слегка исправленная) программа, которую я использую, чтобы убрать взлом на одном из моих производственных серверов. Он использует File :: Find :: Rule, а не File :: Find. File :: Find :: Rule имеет красивый декларативный интерфейс, который легко читается.

Рэндал Шварц также написал File :: Finder как обертку над File :: Find. Это довольно мило, но на самом деле не взлетело.

#! /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 голосов
/ 02 октября 2008

Другие упомянули File :: Find, и я бы попросил итератор, который не является File :: Find (как и File :: Find :: Rule). Возможно, вы захотите взглянуть на File :: Next или File :: Find :: Object , которые имеют итерационные интерфейсы. Марк Джейсон Доминус переходит к созданию своего собственного в главе 4.2.2 Perl высшего порядка .

8 голосов
/ 02 октября 2008

File :: Find является правильным способом решения этой проблемы. Бесполезно переопределять то, что уже есть в других модулях, но переопределение того, что есть в стандартном модуле, действительно не рекомендуется.

4 голосов
/ 05 октября 2008

Это мое File :: Finder , как уже упоминалось, но есть и моё решение "итератор как связанный хэш" из Поисковый поиск файлов (журнал Linux) .

4 голосов
/ 02 октября 2008

Мой предпочтительный метод - использовать модуль File :: Find следующим образом:

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
}
3 голосов
/ 03 октября 2008

Я написал File :: Find :: Closures как набор замыканий, которые вы можете использовать с File :: Find, чтобы вам не приходилось писать свои собственные. Есть пара функций mtime, которые должны обрабатывать

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

Вам на самом деле не нужно использовать модуль, потому что я в основном проектировал его так, чтобы вы могли посмотреть на код и украсть нужные вам части. В этом случае это немного сложнее, потому что все подпрограммы, которые имеют дело со stat, зависят от второй подпрограммы. Вы быстро получите идею из кода.

Удачи,

0 голосов
/ 07 января 2011

Использование стандартных модулей - это действительно хорошая идея, но здесь меня не интересует мое возвращение к базовому подходу без использования внешних модулей. Я знаю, что синтаксис кода здесь не может быть чашей чая для всех.

Можно было бы улучшить использование меньшего количества памяти за счет предоставления доступа к итератору (список ввода может быть временно заблокирован после достижения определенного размера), а условная проверка может быть расширена с помощью обратного вызова 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");
0 голосов
/ 02 октября 2008

Я рискую получить отрицательное голосование, но команда IMHO 'ls' (с соответствующими параметрами) делает это наиболее известным и эффективным способом. В этом случае это может быть довольно хорошим решением для передачи 'ls' из кода perl через shell, возвращая результаты в массив или хэш.

Редактировать: Также можно использовать 'find', как предложено в комментариях.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...