Lammey
Lammey

Reputation: 179

How can Perl verify that a path is a directory yet not be able to call opendir() on the very same?

I'm trying to find empty subdirectories and delete them. I'm sure there are better ways to achieve this (I'm a poor programmer and relatively new with Perl) but even so, I'd like to understand what's wrong with my approach.

use strict;
use warnings;
use File::Basename; 
use File::Find

my $lambda2 = sub
{
    my $path = $File::Find::name;
    if ( -d $path )
    {
        print("Directory: ", $path, "\n");

        # Define anonymous function to test if directory is empty
        my $hasContent = sub
        {
            my $directory = shift;
            opendir ( my $dh, $directory );
            return scalar ( grep { $_ ne "." && $_ ne ".." } readdir ( $dh ) );
        };
        # Remove item if it is an empty directory
        if ( ! $hasContent->( $path ) )
        {
            rmdir( $path );
        }
    }
};
my $directory = "/Users/username/testdir/";

find( { wanted => $lambda2, no_chdir => 1 }, $directory );

If testdir has an empty subdirectory called testsubdir, say, I get the seemingly contradictory response:

Directory: /Users/username/testdir
Directory: /Users/username/testdir/testsubdir
Can't opendir(/Users/username/testdir/testsubdir): No such file or directory

The printing of the latter directory implies that it passed the -d check, but the subsequent error message says there is no such directory. As far as I can see nothing occurs inbetween.

Upvotes: 3

Views: 197

Answers (3)

zdim
zdim

Reputation: 66883

The code's removing directories under find's feet, so to speak.

The simplest fix: change find to finddepth, for postorder traversal, since

it invokes the &wanted function for a directory after invoking it for the directory's contents.

(original emphasis)   Then it won't attempt to invoke wanted on the directory just removed.

Or, merely collect the list of empty directories in find and delete them after find completes.

Upvotes: 5

Dudi Boy
Dudi Boy

Reputation: 4865

Using du and awk.

List all empty directories under $target_directory

 du $target_directory| awk '$1=="0"{print $2}'

Remove all empty directories under $target_directory

 du $target_directory| awk '$1=="0"{system("rmdir "$2);}'

Upvotes: 0

mob
mob

Reputation: 118605

Let's throw in some logging statements and see what is happening:

my $lambda2 = sub {
    my $path = $File::Find::name;
    if ( -d $path ) {
        print("Directory: ", $path, "\n");
        my $hasContent = sub {
            my $directory = shift;
            opendir ( my $dh, $directory );
            return scalar ( grep { $_ ne "." && $_ ne ".." } readdir ( $dh ) );
        };
        my $hc = $hasContent->($path);
        print STDERR "hc($path) = $hc\n";
        if (! $hc) {
            print STDERR "Deleting $path\n";
            rmdir( $path );
        }
    }
};

$ mkdir -p /Users/username/testdir/testsubdir
$ perl subdir.pl
Directory: /Users/username/testdir
hc(/Users/username/testdir) = 1
Directory: /Users/username/testdir/testsubdir
hc(/Users/username/testdir/testsubdir) = 0
Deleting /Users/username/testdir/testsubdir
Can't opendir(/Users/username/testdir/testsubdir): No such file or directory
 at subdir.pl line 26.

So the code is more or less working as designed, it's just that File::Find is trying to walk /Users/username/testdir/testsubdir after you have deleted it.

Upvotes: 4

Related Questions