Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much

Recursive Subdirectories

by pildor (Novice)
on Apr 16, 2003 at 16:27 UTC ( #250957=perlquestion: print w/replies, xml ) Need Help??

pildor has asked for the wisdom of the Perl Monks concerning the following question:


having only been programming with Perl for a week. I decided to write a Perl program to check for files on my Windows 2K server that have not been accessed in a certain amount of time (i.e. 10 days in this example) and store there names in a log file. Everything works great, expect I can not figure out how to read the filenames in subdirectories. I am using Perl 5.8.0 under Windows XP. Any help would be greatly appreciated!

Here is my novice code:

opendir(HERE, '.'); open(OUT, '>fileout.txt') or die "Couldn't open the " . "fileout.txt file for writing.\n"; @AllFiles = readdir(HERE); foreach $Name (@AllFiles) { # If directory skip to next filename if(-d $Name) { next } $FileAcc = sprintf('%3d', -A $Name); # Check for files that have been accessed in the last 10 days if ($FileAcc <= 10) { print "$Name "; print "File was last Accessed ", $FileAcc, " days ago\n"; printf OUT $Name . "\n"; } else { print $Name, " File was Accessed more than 10 days ago\n"; } } closedir(HERE); close(OUT);

Replies are listed 'Best First'.
Re: Recursive Subdirectories
by Ovid (Cardinal) on Apr 16, 2003 at 16:37 UTC

    You're in luck! Recursively searching directories is much easier than this.

    use strict; use File::Find; find (\&wanted, '.'); sub wanted { next if -d; # skip if it's a directory # $_ is the name of the file # $File::Find::name is the full path and name to the file }

    See perldoc File::Find for more information.

    With the above stub, just fill in the logic in the &wanted subroutine.


    New address of my CGI Course.
    Silence is Evil (feel free to copy and distribute widely - note copyright text)

      Recursively searching directories is much easier than this.
      Even easier than File::Find is File::Find::Rule
      use File::Find::Rule; my @files = find( file => exec => sub { -A < (time - (3600 * 24 * 10) }, in => '.', );
      See. the File::Find::Rule docs for more info on this fantabulous module.


        exec => sub { -A < (time - (3600 * 24 * 10) },
        I was going to write the following:

        The module lets you write that more convieniently as:

        accessed => "<10",
        But on testing I found there is a subtle difference between the '-A' operator and the return value of atime from stat() (which FFR's 'atime' method uses) (update: and 'accessed' is just a binary method, it's 'atime' that uses stat) that makes both of our answers wrong. '-A' returns the length of time in days since the last access, and stat's atime is the last access time in epoch seconds. So the correct answer would be a combination of our answers:
        my $time = time - (3600 * 24 * 10); ... atime => ">=$time", # Or it would be about as easy to just say exec => sub { -A < 10 },
        Update: I was initially trying to use 'accessed' incorrectly, then I later found I should have been using 'atime'. Updated code. In FFR, 'accessed' is a binary/boolean method (which makes it practically useless) and doesn't take arguments. All '-X' operators are mapped to boolean methods in FFR, it would be nice to change this.
Re: Recursive Subdirectories
by marinersk (Priest) on Apr 16, 2003 at 17:09 UTC
    The File::Find and related solutions provided are the best answers to getting the task done.

    However, so you understand the mechanics of directory recursion, as well as a bit of codestyle cleanup, I went ahead and modified your snippet to do what you ask without File::Find.

    Update: Moved the report file open/close to the main function and corrected one of the comments.

    Examine at your leisure:

    #!/usr/bin/perl use strict; my $DIRSEP = '\\'; # FIXME This is for DOS/Windows. Make this mor +e portable. open(OUT, '>fileout.txt') or die "Couldn't open the " . "fileout.txt file for writing.\n"; &CheckFileAccess('.'); close(OUT); exit; sub CheckFileAccess() { my $BaseDir = $_[0]; opendir(HERE, $BaseDir); my @AllFiles = readdir(HERE); closedir(HERE); # Pass 1 for files, Pass 2 for directory recursion for (my $curpas = 0; $curpas < 2; $curpas++) { foreach my $Name (@AllFiles) { my $RelName = $BaseDir . $DIRSEP . $Name; if (!$curpas) { # Process Files only; if directory skip to next filena +me if(-d $RelName) { next } my $FileAcc = sprintf('%3d', -A $RelName); # Check for files that have been accessed in the last +10 days if ($FileAcc <= 10) { print "$RelName "; print "File was last Accessed ", $FileAcc, " days +ago\n"; # ------------------------------------------------ +--------- # DANGER printf could have produced interesting a +nd # unexpected results here, so I changed it to prin +t. # ------------------------------------------------ +--------- # printf OUT $Name . "\n"; print OUT $RelName . "\n"; } else { print $RelName, " File was Accessed more than 10 d +ays ago\n"; } } else { # Process Directories only if(-d $RelName) { # Skip danger directories if ($Name eq '.') { next } if ($Name eq '..') { next } # Good to go -- prepare for recursive call &CheckFileAccess($RelName); } } } } }
      Thank you so much for your help!
      You helped me a lot by not using the File:Find module and providing me with some codestyle cleanup!
      By doing this, you allowed me to become a much better Perl Programmer!
Re: Recursive Subdirectories
by Jenda (Abbot) on Apr 16, 2003 at 16:43 UTC

    This is a task for the File::Find module. The module will recurse through the directory structure for you and call the function you want for each file found. This may be a little confusing if you come from VB or something similar. The find() function from File::Find is like a servant that gets a list of things to do and the place to start from and goes through all files and does whatever is in the list.

    use File::Find; sub toDo { return if (-d $_); # I'm not interested in directories if (/\.txt$/i) { print "I found text file $_ in $File::Find::dir\n" } } find( \&toDo, '.');

    P.S.(OT): Yeah the absence to references/pointers to functions is what I hate most about VB.
    All those who understood the topic I just elucidated, please verticaly extend your upper limbs.
       -- Ted Goff

Re: Recursive Subdirectories
by pemungkah (Priest) on Apr 16, 2003 at 17:32 UTC
    Certainly File::Find can help you out.
    #!/usr/bin/perl -w use strict; use File::Find; my @qualifiers = (); my $today = time; my $ten_days = ( 60 # seconds in a minute * 60 # minutes in an hour * 24 # hours in a day * 10 ); # the number of days sub process_file { push @qualifiers, $File::Find::name if is_ten_days_old(); } sub is_ten_days_old { # File::Find has done the following for you: # - chdir to the directory where this file resides # - set $_ to the basename of the file # - set $File::Find::name to the fully-qualified name of the file # Don't check ages on directories. return 0 if -d; # stats $_; ninth item is modification date my $age = (stat(_))[9]; # returns true if old enough, false if not return $today - $age > $ten_days; } find(\&process_file, @ARGV); # Print all the files qualifying: foreach (@qualifiers) { print $_,"\n"; }
    Then invoke it like this:
    C:> perld C:\this\directory\please
    (I think the invocation is right; I 'm a Mac and Unix guy, not Windows. I've run this code on OS X and it works OK there, modulo the file specification syntax being different.) The key is the is_ten_days_old() sub, which tells process_file() which files to keep.

    I highly recommend the Perl Cookbook; if you don't have a copy, you should consider getting one. This is a modification of Recipe 9.7, Processing All Files in a Directory Recursively.

    You could do it yourself, but File::Find is faster for you to get the job done.

Re: Recursive Subdirectories
by !unlike (Beadle) on Apr 16, 2003 at 16:50 UTC

    You need to seperate your searching code off into a subroutine. Something like this might work:

    parse_dir('.'); sub parse_dir { my $path = shift; opendir(HERE, $path) or die "$!\n"; while(readdir(HERE)) { next if /^\.(\.)[0,1]$/; parse_dir($_) if -d $_; #parse files here }

    I'm sure others will offer a better solution.


    I write my Perl code like how I like my sex: fast and dirty. ;)

Re: Recursive Subdirectories
by t'mo (Pilgrim) on Apr 18, 2003 at 05:26 UTC

    Two ideas. 1) In the spirit of TMTOWTDI ("there's more than one way to do it"), and since you're running this on Windows, why not borrow some COM objects, or more specifically, the "Scripting.FileSystemObject". 2), Really, this probelm is nothing more than a depth-first traversal of a tree; File::Find just does the traversal for you. The beauty of combining these two ideas is that you still can see what your program does (i.e., the recursive nature of the problem and solution is hidden in File::Find), but because the FileSystemObject has methods giving you either the files or the directories: you don't have to check to see whether you have a file or a directory from readdir.

    The following is not unlike a WSH program I recently had to hack together...except that the Perl hasn't been tested, since (in theory) I work in a Microsoft-only shop...:

    #!perl -w use strict; use Win32::OLE qw(in); # configure this for your starting directory/path my $start = "c:\\temp"; sub visit { my $file = shift; # broken!!! # ...logic here to check date of file, and print it; # just don't forget that it's a "Windows" object, not a # file descriptor or file/path or something else... #if ( $file->DateLastModified > 3 ) { print $file->name . "\n" } } my $fso = Win32::OLE->new("Scripting.FileSystemObject"); sub scan_directory { my $path = shift; print "Scanning $path...\n"; my $folder = $fso->GetFolder($path); foreach my $subdir ( in $folder->SubFolders ) { scan_directory($path . "\\" . $subdir->Name); } foreach my $file ( in $folder->Files ) { visit($file) } } scan_directory($start);

    p.s. Thanks to this reference, which indirectly pointed out that I should use Win32::OLE 'in'; I was able to actually make this thing *almost* work.

    ...every application I have ever worked on is a glorified munger...

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://250957]
Approved by broquaint
Front-paged by particle
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others making s'mores by the fire in the courtyard of the Monastery: (3)
As of 2023-09-30 01:55 GMT
Find Nodes?
    Voting Booth?

    No recent polls found