Perl挑战-目录迭代器

时间:2020-03-06 15:01:10  来源:igfitidea点击:

我们有时会听到有关Perl的说法,即可能有6种不同的方法来解决相同的问题。优秀的Perl开发人员通常会在各种可能的实现方法之间做出选择时具有深刻的见解。

因此,一个Perl问题的例子:

一个简单的脚本,该脚本以递归方式遍历目录结构,查找最近被修改的文件(在某个日期之后,该变量将是可变的)。将结果保存到文件。

对于Perl开发人员来说,这个问题是:实现此目标的最佳方法是什么?

解决方案

我编写了一个子程序,该子程序使用readdir读取目录,并抛出"。"。和" .."目录,如果找到新目录,则进行递归操作,并检查文件以查找所需内容(在情况下,我们将要使用" utime"或者" stat")。到递归完成时,每个文件都应该已经检查过。

我认为此脚本所需的所有功能均在此处进行了简要说明:
http://www.cs.cf.ac.uk/Dave/PERL/node70.html

输入和输出的语义是一个相当琐碎的练习,我将留给我们。

我冒着被低估的风险,但是恕我直言," ls"(带有适当的参数)命令以一种最著名的性能方式做到了。在这种情况下,将perl代码中的" ls"通过外壳传递给外壳,然后将结果返回到数组或者哈希中,可能是一个很好的解决方案。

编辑:也可以按照评论中的建议"找到"使用。

File :: Find是解决此问题的正确方法。重新实现其他模块中已经存在的东西没有任何用处,但实际上不建议重新实现标准模块中的东西。

我的首选方法是使用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
}

解决问题的地方主要是由标准库使用它们。

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

没有六种方法可以做到这一点,有旧的方法和新的方法。旧方法是使用File :: Find,并且我们已经有一些示例。 File :: Find有一个非常糟糕的回调接口,这在20年前很酷,但是从那时起我们就继续前进。

这是我用来清除其中一台生产服务器上的问题的真实生活(经过轻微修改)的程序。它使用File :: Find :: Rule,而不是File :: Find。 File :: Find :: Rule具有一个易于阅读的声明式接口。

Randal Schwartz还写了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也不是)。我们可能需要查看File :: Next或者File :: Find :: Object,它们确实具有迭代接口。马克·杰森·多米纳斯(Mark Jason Dominus)在高阶Perl的4.2.2章中继续构建我们自己的模型。

我将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的所有子例程都依赖于第二个子例程。但是,我们将从代码中快速获得想法。

祝你好运,

如前所述,有我的File :: Finder,但也有我从渐进式查找文件(Linux Magazine)中获得的作为哈希器的迭代器解决方案。