How can I find the newest .pl file in a directory and all its subdirectories using Perl?
Asked Answered
F

3

4

How can I scan an entire directory's contents, including its subdirectories' contents, and find the newest .pl file within them using Perl?

I want to build a sorted array/list of the full file paths of all .pl files within a directory tree.

So, for example, if my base directory is /home/users/cheeseconqueso/ I want to search for .pl files in that directory and any subdirectory within that path and then sort the .pl files by date.

The end result would be an array, @pl_paths, where $pl_paths[0] would be something like /home/users/cheeseconqueso/maybe_not_newest_directory/surely_newest_file.pl

From that result, I want to execute the file, but I think once I get the sorted array figured out, executing the file in $pl_paths[0], won't be a problem.

There is a similar question on SO that I have been trying to modify to suit my needs, but I am here now for obvious reasons.

The code I'm using to get the newest file NAME only in one directory is:

opendir(my $DH, $DIR) or die "Error opening $DIR: $!";
my %files = map { $_ => (stat("$DIR/$_"))[9] } grep(! /^\.\.?$/, readdir($DH));
closedir($DH);
my @sorted_files = sort { $files{$b} <=> $files{$a} } (keys %files);
print $sorted_files[0]."\n";
Fullfaced answered 28/9, 2011 at 14:51 Comment(0)
G
13

You can use File::Find if you want a core module for this, but I would prefer to use File::Find::Rule.

To start off, we can find all of the .pl files under a directory with

use File::Find::Rule;
my @files = File::Find::Rule->file
                            ->name('*.pl')
                            ->in($directory);

Then let's use map to associate filenames with their modification times:

my @files_with_mtimes = map +{ name => $_, mtime => (stat $_)[9] }, @files;

And sort them by mtime:

my @sorted_files = reverse sort { $a->{mtime} <=> $b->{mtime} } 
                @files_with_mtimes;

And from there, the name of the newest one is in $sorted_files[0]{name}.

If you only want to find the top one, there's actually no need to do a complete sort, but the nicest solution I can think of involves some slightly advanced FP, so don't worry about it at all if it looks strange to you:

use List::Util 'reduce';
my ($top_file) = reduce { $a->{mtime} >= $b->{mtime} ? $a : $b } 
  @files_with_mtimes;
Gallfly answered 28/9, 2011 at 15:4 Comment(8)
I think you misread something in the question.... I'm looking for the newest file, not the biggest fileFullfaced
@Fullfaced oops! Will update that in a moment. Same idea applies though!Gallfly
@Zaid it can't, the plus is there to make sure that it's not interpreted as a code block; it's a hashref.Gallfly
Deceptive use of a hashref right next to map, I guess clothing it in the optional codeblock curlies is a matter of personal preference.Banebrudge
reverse sort { f($a) <=> f($b) } @list could also be written as sort { f($b) <=> f($a) } @listBanebrudge
@Banebrudge and it could also be written reverse sort... which is clearer, don't you think? And the optimizer takes care of it anyway.Gallfly
@Gallfly : That comment was meant more for the readers than for youBanebrudge
Fair enough. And yeah, about the codeblock thing -- I think that map +{ mumble => hash}, ... is still less evil than map { +{ mumble => hash } } ....Gallfly
B
5

With File::Find::Rule, and Schwartzian transform, you can get the newest file with .pl extension, in a subtree starting from dir_path.

#!/usr/bin/env perl

use v5.12;
use strict;
use File::Find::Rule;

my @files = File::Find::Rule->file()->name( '*.pl' )->in( 'dir_path' );

# Note that (stat $_ )[ 9 ] yields last modified timestamp
@files = 
   map { $_->[ 0 ] }
   sort { $b->[ 1 ] <=> $a->[ 1 ] }
   map { [ $_, ( stat $_ )[ 9 ] ] } @files;

# Here is the newest file in path dir_path
say $files[ 0 ];

The map-sort-map chain is a typical idiom: getting timestamp is slow, so we do it only one time per file, keeping every timestamp with its file in an arrayref. Then we sort the new list using timestamp ( comparing the second element of each arrayref ), and finally we discard timestamps, keeping only filenames.

Blower answered 28/9, 2011 at 15:30 Comment(0)
A
2

Use the File::Find core module.

Arvizu answered 28/9, 2011 at 15:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.