Вопрос

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

Итак, пример задачи Perl:

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

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

Это было полезно?

Решение

Похоже, это работа для Файл::Найти::Правило:

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

Другие советы

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

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

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

Вот реальная (слегка измененная) программа, которую я использую для очистки от мусора на одном из моих производственных серверов.Он использует File::Find::Правило, а не 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);
    }
}

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

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

Мой предпочтительный метод - использовать модуль 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
}

Вот мой Файл::Finder, как уже упоминалось, но есть также мое решение iterator-as-a-tied-hash от Пошаговый поиск файлов (журнал Linux).

Я написал Файл::Найти::Замыкания как набор замыканий, которые вы можете использовать с 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->();

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

Удачи вам,

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

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

Я пишу подпрограмму, которая считывает каталог с readdir, выбрасывает каталоги "." и "..", выполняет рекурсию, если находит новый каталог, и проверяет файлы на предмет того, что я ищу (в вашем случае вы захотите использовать utime или stat).К моменту завершения рекурсии каждый файл должен быть проверен.

Я думаю, что все функции, которые вам понадобятся для этого скрипта, кратко описаны здесь:http://www.cs.cf.ac.uk/Dave/PERL/node70.html

Семантика ввода и вывода - довольно тривиальное упражнение, которое я оставляю вам.

Я рискую получить отрицательный результат, но ИМХО команда 'ls' (с соответствующими параметрами) делает это самым известным производительным способом.В этом случае было бы неплохим решением передать 'ls' из perl-кода через shell, возвращая результаты в массив или хэш.

Редактировать:Это также может быть использовано как "найти", как предложено в комментариях.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top