Perl について、同じ問題にアプローチするには 6 つの異なる方法があるかもしれないと言うのを時々耳にします。優れた Perl 開発者は通常、考えられるさまざまな実装方法の中から選択を行うための十分な理由のある洞察を持っています。
Perl の問題の例:
ディレクトリ構造を再帰的に反復し、最近変更されたファイルを検索する単純なスクリプト (特定の日付以降、変数になります)。結果をファイルに保存します。
Perl 開発者への質問: これを達成する最善の方法は何ですか?
Perl について、同じ問題にアプローチするには 6 つの異なる方法があるかもしれないと言うのを時々耳にします。優れた Perl 開発者は通常、考えられるさまざまな実装方法の中から選択を行うための十分な理由のある洞察を持っています。
Perl の問題の例:
ディレクトリ構造を再帰的に反復し、最近変更されたファイルを検索する単純なスクリプト (特定の日付以降、変数になります)。結果をファイルに保存します。
Perl 開発者への質問: これを達成する最善の方法は何ですか?
これは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はうまく機能します。
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";
}
}
これを行うには6つの方法はありません。古い方法と、新しい方法があります。古い方法はFile::Findを使用する方法であり、すでにいくつかの例があります。File :: Findにはかなりひどいコールバックインターフェースがあり、20年前はクールでしたが、それ以来、私たちは先に進んでいます。
これは、実稼働サーバーの1つにある問題を解決するために使用する実際の(少し修正された)プログラムです。File::FindではなくFile::Find::Ruleを使用します。File :: Find :: Ruleには、読みやすい優れた宣言型インターフェースがあります。
Randal Schwartzは、File::FindのラッパーとしてFile::Finderも作成しました。それはかなりいいですが、それは実際には離陸していません。
#! /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 ではない (File::Find::Rule ではない) イテレータを要求しました。File::NextまたはFile::Find::Objectを見たいと思うかもしれませんが、これらは反復インターフェースを持っています。Mark Jason Dominus は、 Higher Order Perlの 4.2.2 章で独自のビルドについて説明しています。
すでに述べたように、私のFile :: Finderがありますが、 Finding Files Incrementally(Linux Magazine)のiterator-as-a-tied-hashソリューションもあります。
私の好みの方法は、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::Closuresは、File::Find で使用できるクロージャーのセットとして作成したので、独自に作成する必要はありません。処理する必要がある mtime 関数がいくつかあります
File::Find を使用します。 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 ); 私の @modified = $list_reporter->();
モジュールを実際に使用する必要はありません。コードを見て、必要な部分を盗むことができるように設計したからです。この場合、stat を処理するすべてのサブルーチンが 2 番目のサブルーチンに依存しているため、少し複雑です。ただし、コードからすぐにアイデアを得ることができます。
幸運を、
標準モジュールを使用することは確かに良い考えですが、ここで興味深いのは、外部モジュールを使用しない基本的なアプローチに戻ることです。ここでのコード構文は、みんなのお茶ではないかもしれないことを私は知っています。
イテレータアクセスを提供することでメモリの使用量を減らすことができ(入力リストが特定のサイズに達すると一時的に保留になる可能性があります)、コールバック参照を介して条件付きチェックを拡張できます。
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
入力と出力のセマンティクスは、かなり些細なことですが、これはあなたにお任せします。
反対票を投じるのは危険ですが、IMHO'ls'(適切なパラメータを使用)コマンドは、最もよく知られているパフォーマンスの高い方法でそれを実行します。この場合、perlコードからシェルを介して「ls」をパイプ処理し、結果を配列またはハッシュに返すことは非常に良い解決策かもしれません。
編集:コメントで提案されているように、「検索」を使用することもできます。