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

Hi,

I have a question that maybe someone can help me with. I am writing/creating a file on a unix server, and I check to see if the file exists before I create it. If it does create a new file, I want to send the previous file via e-mail to a specific person.

I currently have it working for the previous day, I use the date for the filename. But, what if the day before is not the previous file? I want to code something that will either, check the date on the file, or find the previous file, whether it be the day before, i.e. 051104.txt to 051204.txt or some day distant, i.e. 050804.txt to 051204.txt.

I hope I am explaining myself correctly, feel free to ask as many questions you can if i have explained myself well enough.

Joseph A. Ruffino Automated Systems Assistant Gail Borden Public Library District 270 N. Grove Ave Elgin, Il, 60120 847-742-2411 x 5986 jruffino@nsls.info

Replies are listed 'Best First'.
Re: Find a file
by dave_the_m (Monsignor) on May 11, 2004 at 21:33 UTC
    If I underastand you correctly, you have a program that does two things: first it appends data to a file named after today's date, eg a log file; second, if that file doesn't exist, then it first finds the most recent file with an older date in its filename, then creates the new file.

    I would recomment choosing a filename format that sorts alphabetically the same as cronologically, eg 2004.05.11.txt. Then all you need to do is read in the current directory, do a reverse sort on the filenames, and pick the first, eg

    opendir D, '.'; @files = reverse sort grep $_ ne '.' && $_ ne '..', readdir D; if (@files) { email($files[0]); }
    If you can't make your filenames sort like that, then you'll need to take the extra step of splitting apart the filename, eg
    @files = map { $_->[0] } sort { $b->[1] <=> $a[1] } map { /^(..)(..)(..)/; [ $_, "$3$2$1" ] } grep $_ ne '.' && $_ ne '..', readdir D;
    (The above is known as a Schwartzian transform)
Re: Find a file
by shemp (Deacon) on May 11, 2004 at 21:22 UTC
    In general, you'll need to examine all the file names that could be the most recent one, and determine the most recent one from the names. It might be easier if you use file names of the format YYYYMMDD.txt, or some such, because then, after sorting the files, the 'last' one in the sorted list will be the one you want. (Y = year, M = month, D = day)
    ... # this assumes all files in the same dir opendir HANDLE, "the_dir" or die "couldnt opendir the_dir for reading $!"; my @files = readdir HANDLE; closedir(HANDLE); my $most_recent = (sort my_filename_sorter @files)[-1] ... sub my_filename_sorter { (my $a_date = $a) =~ s/\.txt$//; (my $b_date = $b) =~ s/\.txt$//; return $a_date <=> $b_date; }
    I wasn't horribly terse, so hopefully this makes sense.
    Of course you'll need to check for the situation where there are no files.

    UPDATE: I forgot to put the final slash of the substitutions - its there now.
Re: Find a file
by pelagic (Priest) on May 11, 2004 at 21:24 UTC
    Look at file test operators
    -f $file -M $file
    to test file existence and age.

    pelagic
Re: Find a file
by TilRMan (Friar) on May 12, 2004 at 02:21 UTC

    As others pointed out, it's good form to use YYYYMMDD. But in case you can't:

    #!/usr/bin/perl -w use strict; my $DIR = '.'; use File::Find qw( find ); use File::Spec::Functions qw( catfile ); use Time::Local qw( timelocal ); my $todaysfile = catfile($DIR, time_to_filename(time)); if (! -e $todaysfile) { if (my $oldfile = find_latest_file($DIR)) { email_to_a_specific_person($oldfile); } } open FILE, ">>$todaysfile" or die qq{Can't open "$todaysfile": $!}; print FILE "Hello, World!\n"; close FILE; exit 0; sub find_latest_file { my ($dir) = @_; my ($bestfile, $latest) = (undef, 0); find sub { my $time = filename_to_time($_) or return; if ($time > $latest) { ($bestfile, $latest) = ($File::Find::name, $time); } }, $dir; return $bestfile; } sub filename_to_time { my ($filename) = @_; # Adjust if necessary my ($m, $d, $y) = $filename =~ /(\d\d)(\d\d)(\d\d)/ or return; return timelocal(0, 0, 0, $d, $m - 1, $y); } sub time_to_filename { my ($time) = @_; # Adjust if necessary my @localtime = localtime $time; my ($d, $m, $y) = @localtime[3, 4, 5]; my $filename = sprintf("%02d%02d%02d.txt", $m+1, $d, $y%100); return $filename; } sub email_to_a_specific_person { my ($file) = @_; print "Sending $file . . .\n"; }

    If you are planning on running more than one copy of the program at once, then this code will sometimes fail, and you will need to investigate file locking.

    I should've used strftime() from POSIX and could've used reduce() from Scalar::Util. Note that find_latest_file() runs in O(n), which might make a difference (versus an n log n sort) after a few years.