1

What I need to do is wrote a script that will read in a list of directories and then sort them and the last directory will be "poped" off.

my $last_one = pop @sorted;

Then that last directory is going to be removed - with a system("rm- rf $last_one) or remove_tree($last_one).

 1  #!/usr/bin/perl
 2  use strict;
 3  use warnings;
 4
 5  my $dir_to_process = "/production/log/fo/archive/";
 6  opendir DH, $dir_to_process or die "Sorry, this is not going to work out $!";
 7
 8  while (my $name = readdir DH) {
 9       next if $name =~ /^\./;
10       push(my @unsorted,$name) ;
11       my @sorted_dir = sort @unsorted;
12       foreach my $sorted (@sorted_dir) {
13       print "$sorted\n";
14       sleep 1 ;
15       }
16
17  }

However I am having alot of trouble sorting the directories - they are written by this format. This is the actual output.

2013Nov12
2013Sep14
2013Jul15
2013Jan20
2013Sep11
2013May31
2013Jul04
2012Dec09
2013Oct12
2013Oct09
2012Dec27
2013Nov28
2013Mar24
2013Jun06
2013Jun25
3
  • You say "sorting by ASCII" in your title but I assume what you really want is to sort by date (i.e. oldest to newest or vice versa)? Commented Dec 16, 2013 at 16:17
  • 1
    Sorting makes no sense if you just want to find the oldest. Commented Dec 16, 2013 at 16:23
  • the problem is that it is sorting by ascii, and not by date - yes what i need is the oldest dated directory. Commented Dec 16, 2013 at 17:06

4 Answers 4

5

Let's presume the existence of a function convert_date that converts your date to the YYYYMMDD format. If you had a that, a simple string comparison would find the oldest.

my ($oldest) =
   sort { convert_date($a) cmp convert_date($b) }
   @dirs;

Faster:

my ($oldest) =
   map $_->[0],
   sort { $a->[1] cmp $b->[1] }
   map [ $_, convert_date($_) ],
   @dirs;

Fastest:

my ($oldest) =
   map substr($_, 8),
   sort
   map convert_date($_) . $_,
   @dirs;

But sorting (O(N log N)) is a wasteful way of finding one element (O(N)).

my $oldest = $dirs[0];
for (@dirs) {
   $oldest = $_ if convert_date($_) lt $oldest;
}

Faster?

use List::Util qw( minstr );
my $oldest = substr(minstr( map { convert_date($_) . $_ } @dirs ), 8);

Now, all that's left is writing convert_date.

use Carp qw( croak );

my %month_num_by_en_name = (
   Jan => 1,  Feb =>  2,  Mar =>  3,  Apr =>  4,
   May => 5,  Jun =>  6,  Jul =>  7,  Aug =>  8,
   Sep => 9,  Oct => 10,  Nov => 11,  Dec => 12,
);


sub convert_date {
   my ($date) = @_;

   my ($y,$m,$d) = $date =~ m/^(\d{4})(\w{3})(\d{2})\z/
      or croak("Invalid input");

   $m = $month_num_by_en_name{$m}
      or croak("Invalid input");

   return sprintf("%04d%02d%02d", $y,$m,$d);
}

You could also use DateTime::Format::Strptime. This makes it easier to support other languages.

use DateTime::Format::Strptime qw( );

my $format = DateTime::Format::Strptime->new(
   pattern  => '%Y%b%d',
   locale   => 'en_US',
   on_error => 'croak',
);

sub convert_date {
   my ($date) = @_;
   return $format->parse_datetime($date)->strftime('%Y%m%d');
}
Sign up to request clarification or add additional context in comments.

5 Comments

Awesome explanation. Do you mind me asking why you use Carp? Is it out of habit or is there a gain (always talking about the OP's scenario)?
@foibs, Because Carp provides croak, which is appropriate here.
sorry, I wasn't clear. I meant how is croak more appropriate than die. Thanks
It's usually best to use croak/carp instead of die/warn in library code. Because your user (by which I mean the programmer who is using your library) doesn't care where in your code the error is. He cares where he called your function with an invalid parameter.
@DaveCross thanks. It is out of habit then, because here we're talking about a simple script, neither a library, nor a module. It is a good habit I may add.
4

You'll need a function to convert your directory names to dates (splitting the date info into things you can sort: Year, Month, Day).

Here's an example

sub parsedate {
  my $name = shift;
  my %months = ( 'Jan'=> 1, 'Feb' => 2, 'Mar' => 3, 'Apr' => 4,
    'May'=> 5, 'Jun' => 6, 'Jul' => 7, 'Aug' => 8,
    'Sep'=> 9, 'Oct' => 10, 'Nov' => 11, 'Dec' => 12 );

  my ($y,$m,$d);
  $name =~ m/^(\d{4})(\w{3})(\d{2})$/ 
    and ($y,$m,$d) = ($1,$months{$2},$3)
    or die "file name $name doesn't match";

  return sprintf("%04d%02d%02d",$y,$m,$d);
}

Now you can just sort them using your new sub parsedate.

my @sorted_dir = sort {parsedate($a) <=> parsedate($b)} @unsorted;

Comments

2

convert the month name to a number, then make a number out of the y m d Then sort the numbers numerically The "schwartzian transform" is used to attach the numbers to the filenames

#!/usr/bin/perl
#
#
use warnings;
use strict;

my %monthval=qw(Jan 01 Feb 02 Mar 03 Apr 04 May 05 Jun 06 Jul 07 Aug 08 Sep 09 O
ct 10 Nov 11 Dec 12);


my @in=qw(2013Nov12
2013Sep14
2013Jul15
2013Jan20
2013Sep11
2013May31
2013Jul04
2012Dec09
2013Oct12
2013Oct09
2012Dec27
2013Nov28
2013Mar24
2013Jun06
2013Jun25);

my @sorted = map{$_->[0]} sort { $a->[1] <=> $b->[1]}  map { my ($y,$m,$d)=/(\d{
4})(\w{3})(\d{2})/; [$_,"$y".$monthval{$m}."$d"]} @in;

foreach my $name (@sorted) {
  print "$name\n";
}

Comments

2

sort() accepts code block where you can define your own sorting algorithm. Just convert your dir names to timestamp and you should go. This will be smth like
sort { date2stamp($a) <=> date2stamp($b) } @unsorted
And inside date2stamp sub use POSIX::mktime() to create right timestamp for date string.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.