What is an easy way to test if a folder is empty in perl? -s, and -z are not working.
Example:
#Ensure Apps directory exists on the test PC.
if ( ! -s $gAppsDir )
{
die "\n$gAppsDir is not accessible or does not exist.\n";
}
#Ensure Apps directory exists on the test PC.
if ( ! -z $gAppsDir )
{
die "\n$gAppsDir is not accessible or does not exist.\n";
}
These above, do not work properly to tell me that the folder is empty. Thanks!
Thanks all! I ended up using:
sub is_folder_empty { my $dirname = shift; opendir(my $dh, $dirname) or die "Not a directory";
return scalar(grep { $_ ne "." && $_ ne ".." } readdir($dh)) == 0; }
A little verbose for clarity, but:
sub is_folder_empty {
my $dirname = shift;
opendir(my $dh, $dirname) or die "Not a directory";
return scalar(grep { $_ ne "." && $_ ne ".." } readdir($dh)) == 0;
}
Then you can do:
if (is_folder_empty($your_dir)) {
....
}
Using grep { ! /^[.][.]?\z/ } readdir $dir_h can be problematic for performance in case the check is done many times and some directories may have many files.
It would be better to short-circuit the moment a directory entry other than . or .. is found.
On Windows XP with ActiveState perl 5.10.1, the following sub seems to be twice as fast as the grep approach on my $HOME with 100 entries:
sub is_dir_empty {
my ($dir) = #_;
opendir my $h, $dir
or die "Cannot open directory: '$dir': $!";
while ( defined (my $entry = readdir $h) ) {
return unless $entry =~ /^[.][.]?\z/;
}
return 1;
}
Or without any grepping or regular expressions - which rules out any chance of weird file names accidentally getting though. Plus slightly faster is my testing.
#!/usr/bin/perl
use strict;
use warnings;
sub is_dir_empty {
return -1 if not -e $_[0]; # does not exist
return -2 if not -d $_[0]; # in not a directory
opendir my $dir, $_[0] or # likely a permissions issue
die "Can't opendir '".$_[0]."', because: $!\n";
readdir $dir;
readdir $dir;
return 0 if( readdir $dir ); # 3rd times a charm
return 1;
}
my #folders = qw( ./ ./empty ./hasonefile ./hastwofiles ./doesnotexist ./afile );
for my $folder ( #folders ) {
print "Folder '$folder' ";
my $rc = is_dir_empty( $folder );
if( $rc == -1 ) {
print "does not exist\n";
} elsif( $rc == -2 ) {
print "is not a directory\n";
} elsif( !$rc ) {
print "is not empty\n";
} else {
print "is empty\n";
}
}
Pretty simple. If you get three valid responses from a call to readdir, then you know there must be a file in there. Regardless of what name the file may have - or the order in which the files are being processed. Would have preferred something called 'is_dir_used' as I personally don't like the double-negative function name and return value.
There is also File::List from cpan. It's overkill here, but can be handy for slightly more complex requests like test if a directory is empty with the meaning it contains only empty directories (ie: not files).
Here is a more consise algorithm using Perl v5.12 (from circa 2010).
At most, three reads of the directory are made.
It does NOT read the whole directory.
It handles non-Unix filesystems that do not have '..'; See 'find' '-noleaf'
Desc: return true if empty, false if not empty; die if opendir() error
sub is_dir_empty {
use 5.012; # so readdir assigns to $_ in a lone while test
local($_); # prevent side effects
my $dir = shift // die "arg missing";
opendir my $dh, $dir or die "opendir(..,$dir): $!";
while ( readdir $dh ) {
return 0 if ! /^ [.][.]? $/x;
}
return 1;
} # is_dir_empty()
Credit to DevShed
if (scalar <directory/*>) {print qq|File Exists\n|}
Edit
To include hidden files:
#arr = <directory/* directory/.*>;
#arr = grep {!/^directory/[.]{1,2}$/} #arr;
if (#arr) { print qq|File or Directory Exists\n| }
Please read the comments as there have been good points made. Despite the negative points this answer has received, it is still correct.
opendir(DIR,"DIR PATH") or die "Unable to open directory \"DIR PATH\" \n";
my #drList = readdir(DIR);
close(DIR);
if( grep(/\w/,#drList) ){ print "Not Empty\n" }
else { print "Empty\n" }
sub is_folder_empty {
my $dirname = shift;
my #files = File::Find::Rule->file()->name('*')->maxdepth(1)->in("$dirname");
return $#files < 0;
}
Related
I am trying to read files inside a folder in Perl using Directory Handle. The script is able to show the file name but it is throwing two errors: readdir() attempted on invalid dirhandle DIR and closedir() attempted on invalid dirhandle DIR.
I am calling a subroutine and passing two values:
if($fileEnding eq "directory")
{
print "$fileName is a directory\n";
FolderInvestigator1($a, $fileName);
}
$a holds the directory name and its path which is being passed via command-line argument. I am passing the control to a subroutine.
Below is my code:-
sub FolderInvestigator1
{
my $prevPath = shift;
my $receivedFolder = shift;
my $realPath = "$prevPath/$receivedFolder";
my $path = File::Spec->rel2abs($realPath);
print "$path\n";
print "$receivedFolder Folder Received\n";
opendir(DIR, $path) or die "You've Passed Invalid Directory as Arguments\n";
while(my $fileName = readdir DIR)
{
next if $fileName =~ /^\./;
print "The Vacant Folder has $fileName file\n";
}
closedir(DIR);
}
Here is my complete code:-
FirstResponder();
sub FirstResponder
{
if (#ARGV == 0)
{
print "No Arguments Passed\n";
}
else
{
foreach my $a(#ARGV)
{
print "Investigating $a directory below:-\n";
opendir(DIR, $a) or die "You've Passed Invalid Directory as Arguments\n";
while(my $fileName = readdir DIR)
{
next if $fileName =~ /^\./;
$ending = `file --mime-type $a/$fileName`;
#print $ending;
$fileEnding = `basename -s $ending`;
#print $fileEnding;
chomp($fileEnding);
#print $fileName,"\n";
if($fileEnding eq "directory")
{
print "$fileName is a directory\n";
FolderInvestigator1($a, $fileName);
}
else
{
CureExtensions($a, $fileName);
}
}
closedir(DIR);
my #files = glob("$a/*");
my $size = #files;
if($size == 0)
{
print "The $a is an empty directory\n";
}
}
}#Foreach Ends Here..
}
Please see the screenshot for more information on what's going on!
I am not able to realize why Directory Handle is throwing error even though I made the path correct. Some guidance will be highly appreciated.
The problem with your code is that you have a nested use of the bareword (global) dir handle DIR, and hence the inner loop closes the handle before the outer loop is finished:
opendir(DIR, $arg) or die "...";
while(my $fileName = readdir DIR) {
# ... more code here
opendir(DIR, $path) or die "...";
while(my $file = readdir DIR) {
# ... more code here
}
closedir DIR;
}
closedir DIR;
Here is an example of how you could write the first loop using a lexical dir handle $DIR instead of using a legacy global bareword handle DIR:
use feature qw(say);
use strict;
use warnings;
use File::Spec;
FirstResponder();
sub FirstResponder {
foreach my $arg (#ARGV) {
print "Investigating $arg directory below:-\n";
opendir(my $DIR, $arg) or die "You've Passed Invalid Directory as Arguments\n";
my $size = 0;
while(my $fileName = readdir $DIR) {
next if $fileName =~ /^\./;
my $path = File::Spec->catfile( $arg, $fileName );
if( -d $path) {
print "$fileName is a directory\n";
say "FolderInvestigator1($arg, $fileName)"
}
else {
say "CureExtensions($arg, $fileName)";
}
$size++;
}
closedir $DIR;
if($size == 0) {
print "The $arg is an empty directory\n";
}
}
}
The use of bareword filehandle names is old style and deprecated, according to perldoc open:
An older style is to use a bareword as the filehandle, as
open(FH, "<", "input.txt")
or die "Can't open < input.txt: $!";
Then you can use FH as the filehandle, in close FH and and so on. Note that it's a global
variable, so this form is not recommended in new code.
See also:
Why does Perl open() documentation use two different FILEHANDLE style?
Don't Open Files in the old way
in Perl i need to read file from a parent directory to it's last file it any sub directory is there i need to read those files too!so I've tried something like this with the help of recursive function but it gives infinite loop so can anybody help me!
code;
sub fileProcess{
(my $file_name)=#_;
print "$file_name it is file\n";
}
sub main{
(my $dir)=#_;
chdir $dir;
my $tmp=`pwd`;
my #tmp =<*>;
chomp(#tmp);
foreach my $item(#tmp){
chomp($item);
if(-d $item){
dirProcess("$tmp/$item");
}else{
fileProcess($item);
}
}
}
sub dirProcess{
(my $file_name)=#_;
print ">>the corresponding dir is $file_name<<";
main($file_name);
}
my $home="../../Desktop";
chdir $home;
my $path=`pwd`;
main($home);
Here's a sub that will search recursively :
sub find_files {
my ($dir) = #_;
my (#files, #dirs) = ();
my (#allfiles, #alldirs) = ();
opendir my $dir_handle, $dir or die $!;
while( defined( my $ent = readdir $dir_handle ) ) {
next if $ent =~ /^\.\.?$/;
if( -f "$dir/$ent" ) {
push #files, "$dir/$ent";
} elsif( -d "$dir/$ent" ) {
push #dirs, "$dir/$ent";
}
}
close $dir_handle;
push #allfiles, #{ process_files($_) } for #files;
push #alldirs, #{ find_files($_) } for #dirs;
return \#alldirs;
}
The main reason your code isn't working is that, when dirProcess it calls main again which does chdir to a different directory. That means the rest of the files in the #tmp array aren't found.
To fix it I have just added a chdir $dir after the call to dirProcess. In addition I have
Added use strict and use warnings. Yyou must always put these at the top of your program.
Removed all calls to pwd which were unnecessary. You know what you present working directory is because you've just set it!
Removed unnecessary chomp calls. The information from glob never has trailing newlines. The one string that did need chomping is $tmp but you didn't do it!
It's still not a very nice piece of code, but it works!
use strict;
use warnings;
sub fileProcess {
(my $file_name) = #_;
print "$file_name it is file\n";
}
sub main {
(my $dir) = #_;
chdir $dir;
my #tmp = <*>;
foreach my $item (#tmp) {
if (-d $item) {
dirProcess("$dir/$item");
chdir $dir;
}
else {
fileProcess($item);
}
}
}
sub dirProcess {
(my $file_name) = #_;
print ">>the corresponding dir is $file_name<<\n";
main($file_name);
}
my $home = "../../Desktop";
main($home);
I wrote a program to compare the image files of two folders(having 1000 files each) with some logic (see this SO question).
While executing it is comparing successfully until 900 images but then it gives an error like Use of uninitialized value within #tfiles2 in concatenation (.) or string at C:\dropbox\Image_Compare\image_magick.pl line 55 (#3).
And then I get a popup error like Perl Command Line Interpreter has stopped working, so I close the program.
My code is as follows:
#!/usr/bin/perl
use Image::Magick;
no warnings 'uninitialized';
use warnings;
use diagnostics;
#use strict;
use List::Util qw(first);
my $directory1="C:/dropbox/Image_Compare/folder1";
opendir(DIR, $directory1) or die "couldn't open $directory1: $!\n";
my #files1 = grep { (!/^\./) && -f "$directory1/$_" } readdir(DIR);
closedir DIR;
print #files1;
print 'end of files1';
my $directory2="C:/dropbox/Image_Compare/folder2";
opendir(DIR, $directory2) or die "couldn't open $directory2: $!\n";
my #files2= grep { (!/^\./) && -f "$directory2/$_" } readdir(DIR);
closedir DIR;
print #files2;
print 'end of files2';
print $files1[0];
foreach my $fils2 (#files2)
{
$g1 = Image::Magick->new;
$g2 = Image::Magick->new;
$temp1 = $g1->Read( filename=>"C:/dropbox/Image_Compare/folder1/".$files1[0]."");
$temp1 = $g2->Read( filename=>"C:/dropbox/Image_Compare/folder2/".$fils2."");
$g3 = $g1->Compare( image=>$g2, metric=>'AE' ); # compare
$error1 = $g3->Get( 'error' );
#print $error1;
if ($error1 == '0')
{
print "Matching image is:";
print $fils2 . "\n";
my $tdirectory2="C:/dropbox/Image_Compare/folder2";
opendir(DIR, $tdirectory2) or die "couldn't open $directory2: $!\n";
my #tfiles2 = grep { (!/^\./) && -f "$tdirectory2/$_" } readdir(DIR);
closedir DIR;
#my $index = firstidx { $_ eq'"' .$fils2.'"' } #tfiles2;
my $index = first { $tfiles2[$_] eq $fils2} 0..$#tfiles2;
#print $fils2;
print $index;
my $i=0;
foreach my $fils1 (#files1)
{
print 'ganesh';
print $files1[$i];
print $tfiles2[$index];
print 'gowtham'; print "<br />";
#print #tfiles2;
$g4 = Image::Magick->new;
$g5 = Image::Magick->new;
$temp2 = $g4->Read( filename=>"C:/dropbox/Image_Compare/folder1/".$files1[$i]."");
$temp2 = $g5->Read( filename=>"C:/dropbox/Image_Compare/folder2/".$tfiles2[$index]."");
$g6 = $g4->Compare( image=>$g5, metric=>'AE' ); # compare
$error2 = $g6->Get( 'error' );
$i++;
$index++;
if ($error2 == '0') {}
else {print "Image not matching:"; print $tfiles2[$index]; last;}
#if ($i == '800') {last;}
}
last
}
}
Can anyone please help, where i am doing a mistake.
Folder 1 file names: 0025.bmp to 1051.bmp;
Folder 2 file names: 0000.bmp to 1008.bmp;
Thanks
Ganesh
I don't know which the offending line is, but one of these is likely to be the candidate:
$temp2 = $g5->Read( filename=>"C:/dropbox/Image_Compare/folder2/".$tfiles2[$index]."");
or
else {print "Image not matching:"; print $tfiles2[$index]; last;}
Do note that you increment $index whether or not it is inside the array bounds. You do not check for the condition $index > $#tfiles, which should break the loop.
You might want to assert that both input arrays contain >> 900 elements, by printing the length like print "length: ", scalar #array, "\n";.
You can test at which index the undefined error actually happens by testing for definedness of the elements in the arrays:
if (not defined $tfiles[$index] or not defined $files1[$i]) {
die "There was an undefined element at index=$index, i=$i";
}
But then again, the offset between $i, and $index is constant (as mentioned in my answer), so you don't have to actually carry two variables.
A simple comparator subroutine could make your code more readable, thus aiding debugging (see procedural programming).
# return true if matching, false otherwise.
sub compare_images {
my ($file1, $file2) = #_;
my $image1 = Image::Magick->new;
$image1->Read(filename => $file1);
my $image2 = Image::Magick->new;
$image2->Read(filename => $file2);
my $result = $image1->Compare(image => $image2, metric => 'AE')->Get('error');
# free memory
undef $image1;
undef $image2;
return 0 == $result;
}
called like
my $image_root = "C:/dropbox/Image_Compare";
my ($folder1, $folder2) = qw(folder1 folder2);
unless (compare_images("$image_root/$folder1/$files1[$i]",
"$image_root/$folder2/$tfiles[$index]")) {
print "Images not matching at index=$index, i=$i\n";
print "filename: $tfiles[$index]\n";
last;
}
You could read your directories like
sub get_images_from_dir {
my ($dirname) = #_;
-d $dirname or die qq(The path "$dirname" doesn't point to a directory!);
opendir my $dir => $dirname or die qq(Can't open "$dirname": $!);
my #files = grep {!/^\./ and -f "$dirname/$_"} readdir $dir;
closedir $dir;
unless (#files) { die qq(There were no interesting files in "$dirname".) }
return #files;
}
Steps like these make code more readable and make it easy to insert checks.
Is there a way to check whether there is any subfolder exist inside a folder. I would like to do this in Perl?
Glob through the contents of the directory, and check whether it is a directory with -d.
sub has_subfolder {
my $directory = shift;
for ( <$directory/*>, <$directory/.*> ) {
next if m#/\.\.?$#; # skip . and ..
return 1 if -d;
}
return 0;
}
if (grep -d, glob("$folder/*")) {
print "$folder has subfolder(s)\n";
}
If you want to deal with directories matching .*, you could do:
if (grep -d && !/\.\.?$/, glob("$folder/.* $folder/*")) {
print "$folder has subfolder(s)\n";
}
You can use the'File::Find' module for this purpose. File::Find processes and scans a directory recursively. Here is the sample code:
use File::Find;
my $DirName = 'dirname' ;
sub has_subdir
{
#The path of the file/dir being visited.
my $subdir = $File::Find::name;
#Ignore if this is a file.
return unless -d $subdir;
#Ignore if $subdir is $Dirname itself.
return if ( $subdir eq $DirName);
# if we have reached here, this is a subdirector.
print "Sub directory found - $subdir\n";
}
#For each file and sub directory in $Dirname, 'find' calls
#the 'has_subdir' subroutine recursively.
find (\&has_subdir, $DirName);
In order to check if a subfolder exists in a directory (without knowing any names):
my $dir_name = "some_directory";
opendir my $dir, $dir_name
or die "Could not open directory $dir_name: $!";
my $has_subfolder = grep { -d && !/(^|\/)\.\.?$/ } map { ("$dir_name"||'.')."/$_" } readdir $dir;
In other words, it checks for one or more files in the directory which are themselves directories.
If you want a specific subfolder, just use Geo's answer.
Edit: This is getting silly now, but here's a truly general-purpose answer. :-P Someone else is getting the check mark anyway.
sub hasSubDir {
my $dir_name = shift;
opendir my $dir, $dir_name
or die "Could not open directory $dir_name: $!";
my #files = readdir($dir);
closedir($dir);
for my $file (#files) {
if($file !~ /\.\.?$/) {
return 1 if -d $dir/$file;
}
}
return 0;
}
OK, I'm just gonna have to submit my own answer
sub has_subfolder {
my $dir = shift;
my $found = 0;
opendir my $dh, $dir or die "Could not open directory $dir: $!";
while (my $_ = readdir($dh)) {
next if (/^\.\.?$/); # skip '.' and '..'
my $path = $dir . '/' . $_; # readdir doesn't return the whole path
if (-d $path) { # found a dir? record it, and leave the loop!
$found = 1;
last;
}
closedir($dh); # make sure we cleanup after!
return $found;
}
Compared to other answers:
finds hidden directories
completes as soon as it finds a match
doesn't traverse the tree twice (once for normal files, and again for hidden files)
EDIT - I see the requirements just changed (sigh). Fortunately the code above is trivially modified:
sub get_folders {
my $dir = shift;
my #found;
opendir my $dh, $dir or die "Could not open directory $dir: $!";
while (my $_ = readdir($dh)) {
next if (/^\.\.?$/); # skip '.' and '..'
my $path = $dir . '/' . $_; # readdir doesn't return the whole path
push(#found, $_) if (-d $path) # found a dir? record it
}
closedir($dh); # make sure we cleanup after!
return #found;
}
if(-e "some_folder/some_subfolder") {
print "folder exists";
}
else {
print "folder does not exist";
}
How do I get Perl to read the contents of a given directory into an array?
Backticks can do it, but is there some method using 'scandir' or a similar term?
opendir(D, "/path/to/directory") || die "Can't open directory: $!\n";
while (my $f = readdir(D)) {
print "\$f = $f\n";
}
closedir(D);
EDIT: Oh, sorry, missed the "into an array" part:
my $d = shift;
opendir(D, "$d") || die "Can't open directory $d: $!\n";
my #list = readdir(D);
closedir(D);
foreach my $f (#list) {
print "\$f = $f\n";
}
EDIT2: Most of the other answers are valid, but I wanted to comment on this answer specifically, in which this solution is offered:
opendir(DIR, $somedir) || die "Can't open directory $somedir: $!";
#dots = grep { (!/^\./) && -f "$somedir/$_" } readdir(DIR);
closedir DIR;
First, to document what it's doing since the poster didn't: it's passing the returned list from readdir() through a grep() that only returns those values that are files (as opposed to directories, devices, named pipes, etc.) and that do not begin with a dot (which makes the list name #dots misleading, but that's due to the change he made when copying it over from the readdir() documentation). Since it limits the contents of the directory it returns, I don't think it's technically a correct answer to this question, but it illustrates a common idiom used to filter filenames in Perl, and I thought it would be valuable to document. Another example seen a lot is:
#list = grep !/^\.\.?$/, readdir(D);
This snippet reads all contents from the directory handle D except '.' and '..', since those are very rarely desired to be used in the listing.
A quick and dirty solution is to use glob
#files = glob ('/path/to/dir/*');
This will do it, in one line (note the '*' wildcard at the end)
#files = </path/to/directory/*>;
# To demonstrate:
print join(", ", #files);
IO::Dir is nice and provides a tied hash interface as well.
From the perldoc:
use IO::Dir;
$d = IO::Dir->new(".");
if (defined $d) {
while (defined($_ = $d->read)) { something($_); }
$d->rewind;
while (defined($_ = $d->read)) { something_else($_); }
undef $d;
}
tie %dir, 'IO::Dir', ".";
foreach (keys %dir) {
print $_, " " , $dir{$_}->size,"\n";
}
So you could do something like:
tie %dir, 'IO::Dir', $directory_name;
my #dirs = keys %dir;
You could use DirHandle:
use DirHandle;
$d = new DirHandle ".";
if (defined $d)
{
while (defined($_ = $d->read)) { something($_); }
$d->rewind;
while (defined($_ = $d->read)) { something_else($_); }
undef $d;
}
DirHandle provides an alternative, cleaner interface to the opendir(), closedir(), readdir(), and rewinddir() functions.
Similar to the above, but I think the best version is (slightly modified) from "perldoc -f readdir":
opendir(DIR, $somedir) || die "can't opendir $somedir: $!";
#dots = grep { (!/^\./) && -f "$somedir/$_" } readdir(DIR);
closedir DIR;
You can also use the children method from the popular Path::Tiny module:
use Path::Tiny;
my #files = path("/path/to/dir")->children;
This creates an array of Path::Tiny objects, which are often more useful than just filenames if you want to do things to the files, but if you want just the names:
my #files = map { $_->stringify } path("/path/to/dir")->children;
Here's an example of recursing through a directory structure and copying files from a backup script I wrote.
sub copy_directory {
my ($source, $dest) = #_;
my $start = time;
# get the contents of the directory.
opendir(D, $source);
my #f = readdir(D);
closedir(D);
# recurse through the directory structure and copy files.
foreach my $file (#f) {
# Setup the full path to the source and dest files.
my $filename = $source . "\\" . $file;
my $destfile = $dest . "\\" . $file;
# get the file info for the 2 files.
my $sourceInfo = stat( $filename );
my $destInfo = stat( $destfile );
# make sure the destinatin directory exists.
mkdir( $dest, 0777 );
if ($file eq '.' || $file eq '..') {
} elsif (-d $filename) { # if it's a directory then recurse into it.
#print "entering $filename\n";
copy_directory($filename, $destfile);
} else {
# Only backup the file if it has been created/modified since the last backup
if( (not -e $destfile) || ($sourceInfo->mtime > $destInfo->mtime ) ) {
#print $filename . " -> " . $destfile . "\n";
copy( $filename, $destfile ) or print "Error copying $filename: $!\n";
}
}
}
print "$source copied in " . (time - $start) . " seconds.\n";
}
from: http://perlmeme.org/faqs/file_io/directory_listing.html
#!/usr/bin/perl
use strict;
use warnings;
my $directory = '/tmp';
opendir (DIR, $directory) or die $!;
while (my $file = readdir(DIR)) {
next if ($file =~ m/^\./);
print "$file\n";
}
The following example (based on a code sample from perldoc -f readdir) gets all the files (not directories) beginning with a period from the open directory. The filenames are found in the array #dots.
#!/usr/bin/perl
use strict;
use warnings;
my $dir = '/tmp';
opendir(DIR, $dir) or die $!;
my #dots
= grep {
/^\./ # Begins with a period
&& -f "$dir/$_" # and is a file
} readdir(DIR);
# Loop through the array printing out the filenames
foreach my $file (#dots) {
print "$file\n";
}
closedir(DIR);
exit 0;
closedir(DIR);
exit 0;