2008-10-02 28 views
5

Đôi khi bạn nghe nó nói về Perl rằng có thể có 6 cách khác nhau để tiếp cận cùng một vấn đề. Các nhà phát triển Perl tốt thường có những hiểu biết hợp lý để lựa chọn giữa các phương pháp triển khai khác nhau có thể.Perl Challenge - Directory Iterator

Vì vậy, một ví dụ Perl vấn đề:

Một kịch bản đơn giản mà đệ quy lặp thông qua một cấu trúc thư mục, tìm kiếm file đó đã được sửa đổi gần đây (sau một ngày nào đó, đó sẽ là biến). Lưu kết quả vào một tập tin.

Câu hỏi dành cho nhà phát triển Perl: Cách tốt nhất để thực hiện điều này là gì?

+0

Tôi nghĩ rằng bạn có thể gặp vấn đề với tính đơn giản, thường trong đơn giản perl không phải là giải pháp tốt nhất hay thanh lịch nhất. –

+0

Điểm tốt, cảm ơn. Tôi sẽ để nó cho tất cả mọi người để băm ra câu trả lời của họ nếu họ cảm thấy nó thêm vào giải pháp của họ. – keparo

Trả lời

17

này nghe có vẻ như một công việc cho 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 - mặc dù nó có bất lợi khi không phải là một mô-đun chuẩn. – slim

+3

Không có thứ gì như mô-đun "chuẩn". Nếu bạn có nghĩa là mô-đun đi kèm với perl chính nó, những người thuộc hai loại: lịch sử, hoặc sử dụng trong việc cài đặt các mô-đun khác; không ai trong số đó là lý do chính đáng để thích những người khác từ CPAN. – ysth

-1

Tôi viết một chương trình con đọc thư mục có số readdir, ném ra "." và ".." thư mục, đệ quy nếu nó tìm thấy một thư mục mới, và kiểm tra các tập tin cho những gì tôi đang tìm kiếm (trong trường hợp của bạn, bạn sẽ muốn sử dụng utime hoặc stat). Theo thời gian đệ quy được thực hiện, mọi tập tin nên được kiểm tra.

Tôi nghĩ rằng tất cả các chức năng bạn cần cho kịch bản này được mô tả vắn tắt ở đây: http://www.cs.cf.ac.uk/Dave/PERL/node70.html

Ngữ nghĩa của đầu vào và đầu ra là một bài tập khá tầm thường mà tôi sẽ để lại cho bạn.

+0

Chỉ cần cầu nguyện bạn không có một liên kết tượng trưng trỏ đến một thư mục tổ tiên, nếu không cách tiếp cận đơn giản này sẽ lặp lại mãi mãi. – dland

+0

Tôi thường viết kịch bản trong Windows, đây không phải là vấn đề. Trong Linux, kiểm tra có thể được viết để kiểm tra các liên kết để tránh vấn đề, nếu cần thiết. Nó sẽ tạo ra một vấn đề mặc dù cảm ơn vì đã chỉ nó ra! Cảm ơn bạn đã đề cập đến sự đơn giản của câu trả lời của tôi: đó là những gì được yêu cầu ... – antik

-2

Tôi đang mạo hiểm để được giảm giá, nhưng lệnh IMHO 'ls' (với thông số thích hợp) thực hiện theo cách trình diễn được biết đến nhiều nhất. Trong trường hợp này, nó có thể là giải pháp khá tốt đối với đường ống 'ls' từ mã perl thông qua trình bao, trả về kết quả cho mảng hoặc băm.

Chỉnh sửa: Nó cũng có thể là 'tìm' được sử dụng, như được đề xuất trong nhận xét.

+0

không tốt lắm nếu tập lệnh không được sử dụng trên hệ điều hành * nix. – workmad3

+1

ls không thể thực hiện các lựa chọn phức tạp. Ngoài ra nó không xử lý các dòng mới được nhúng vào tên của các tệp. –

+0

Nếu bạn phải từ bỏ tính di động và gọi lệnh trình bao, thì 'tìm' là lệnh phù hợp với nhu cầu của người hỏi. Tuy nhiên, File :: Find đạt được điều tương tự trong Perl bản địa, và thích hợp hơn. – slim

8

File::Find là cách phù hợp để giải quyết vấn đề này. Không có sử dụng trong reimplementing công cụ đã tồn tại trong các mô-đun khác, nhưng reimplementing cái gì đó là trong một mô-đun chuẩn nên thực sự được nản lòng.

+0

Tệp tin :: Tìm kiếm không làm những việc tối ưu nhất có thể.Nó bỏ qua bất kỳ trở về từ "muốn", vì vậy bạn không thể ngăn chặn nó đi qua các phần của cây bạn không cần, nếu điều đó thậm chí có thể, bởi vì nó tập hợp tất cả các đường dẫn đầu tiên và trả lại cho bạn tất cả cùng một lúc. – Axeman

+1

Tệp :: Tìm không trả lại bất kỳ đường dẫn nào, nó thực hiện cuộc gọi đến chức năng "mong muốn" của bạn. Bạn có thể "die()" trong hàm mong muốn và bẫy nó với một "eval {}" xung quanh find() nếu bạn muốn thoát sớm. Tệp :: Tìm :: Quy tắc trả về tất cả các đường dẫn. – runrig

+0

Nhận xét trên đã trả lời cho Axeman. Ngoài ra, nó là File :: Tìm :: Quy tắc tập hợp tất cả các đường dẫn và trả về tất cả cùng một lúc, và không thể thoát sớm. – runrig

4

phương pháp ưa thích của tôi là sử dụng File :: Tìm mô-đun như vậy:

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

Trong trường hợp vấn đề được giải quyết chủ yếu bởi các thư viện tiêu chuẩn sử dụng chúng.

Tệp :: Tìm trong trường hợp này hoạt động tốt.

Có thể có nhiều cách để thực hiện mọi thứ trong perl, nhưng khi có thư viện chuẩn để làm điều gì đó, nó sẽ được sử dụng trừ khi nó có vấn đề riêng.

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

Không cần phải lấy thời gian hiện tại và chuyển đổi ngày thành giây, ($ days <= -M) sẽ làm – runrig

+0

Hoặc ($ days> = -M), bây giờ tôi đã đọc OP. – runrig

9

Không có sáu cách để làm điều này, có cách cũ và cách mới. Cách cũ là với File :: Find, và bạn đã có một vài ví dụ về điều đó. File :: Find có một giao diện gọi lại khá khủng khiếp, nó đã được mát mẻ 20 năm trước, nhưng chúng tôi đã chuyển từ đó.

Đây là chương trình thực tế (được chỉnh sửa nhẹ) mà tôi sử dụng để xóa bỏ chuyến bay trên một trong các máy chủ sản xuất của mình. Nó sử dụng File :: Find :: Rule, thay vì File :: Find. Tệp :: Tìm :: Quy tắc có giao diện khai báo dễ đọc dễ đọc.

Randal Schwartz cũng đã viết Tệp :: Trình tìm kiếm, dưới dạng trình bao bọc trong Tệp :: Tìm. Nó khá đẹp nhưng nó chưa thực sự cất cánh.

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

Những người khác đã đề cập đến Tệp :: Tìm, đó là cách tôi muốn, nhưng bạn đã yêu cầu trình lặp, tệp nào :: Tìm kiếm không phải (cũng không phải là Tệp :: Tìm :: Quy tắc) . Bạn có thể muốn xem File::Next hoặc File::Find::Object, có giao diện lặp lại. Mark Jason Dominus đi qua xây dựng của riêng bạn trong chương 4.2.2 của Higher Order Perl.

+1

Và để công bằng với MJD, Tệp :: Tiếp theo được trích xuất trực tiếp từ sách của anh ấy. –

3

Tôi đã viết File::Find::Closures làm bộ đóng cửa mà bạn có thể sử dụng với Tệp :: Tìm để bạn không phải tự viết. Có một vài chức năng mtime rằng nên xử lý

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

Bạn không thực sự cần phải sử dụng các mô-đun vì tôi chủ yếu là thiết kế nó như là một cách mà bạn có thể nhìn vào mã và ăn cắp các bộ phận mà bạn muốn. Trong trường hợp này, nó phức tạp hơn một chút vì tất cả các chương trình con xử lý chỉ số phụ thuộc vào chương trình con thứ hai. Bạn sẽ nhanh chóng có được ý tưởng từ mã mặc dù.

Chúc may mắn,

0

Sử dụng các mô-đun chuẩn thực sự là một ý tưởng tốt nhưng không quan tâm ở đây là tôi quay trở lại phương pháp tiếp cận cơ bản bằng cách sử dụng không có mô-đun bên ngoài. Tôi biết cú pháp mã ở đây có thể không phải là tách trà của mọi người.

Nó có thể được cải thiện để sử dụng ít bộ nhớ hơn bằng cách cung cấp truy cập vòng lặp (danh sách đầu vào có thể tạm thời bị giữ khi nó đạt đến kích thước nhất định) và kiểm tra có điều kiện có thể được mở rộng thông qua gọi lại 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");