Arunesh Singh Arunesh Singh - 2 months ago 6
Perl Question

Perl : Read directory until a file is there

I am writing a simple program to check for existence of file in a directory. The file is created by some external program with no uniform time. So, I need to check for the existence of file in a loop as I need to process it as soon as it borns.

This program runs continuously, even if the file has been created.

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


my $dir = "/my/path";
opendir( my $dh, $dir ) || die "can't opendir $dir: $!";
my @files = grep { /^[^\.]/ && -f "$dir/$_" } readdir($dh);
until (@files) {
print "No files Found: Sleeping for a sec\n";
sleep(1);
@files = grep { /^[^\.]/ && -f "$dir/$_" } readdir($dh);
}
print @files;


The below program worked as expected.

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


my $dir = "/my/path";
opendir( my $dh, $dir ) || die "can't opendir $dir: $!";
my @files = grep { /^[^\.]/ && -f "$dir/$_" } readdir($dh);
closedir $dh;
until (@files) {
opendir( my $dh, $dir ) || die "can't opendir $dir: $!";
print "No files Found: Sleeping for a sec\n";
sleep(1);
@files = grep { /^[^\.]/ && -f "$dir/$_" } readdir($dh);
closedir $dh;
}

print @files;


I want to know difference between both of these two approaches. Why the former one didn't work even though I am reading the directory every time in a loop. Why I need to open the directory every time in a loop to make it work?

Answer

This is because after readdir the directory handle reached the end of the filelist so there is nothing more to read the next time, even a file is added in the meanwhile. This happens on the first read when the handle is used in the list context.

You can add a call to rewinddir to the loop, which moves the directory handle back to the beginning of the filename list.

Or, use glob and put the test right in the until condition

my $cond = qr(^[^\.]); 

until (grep { $cond && -f } glob "$dir/*") { 
    say "no files"; 
    sleep 1;
}

This way the directory is re-read for every test. Since glob returns the path this simplifies things.

It can also be shortened to

my @files;

sleep 1 until @files = grep { $cond && -f } glob "$dir/*";

say for @files;