Reputation: 1900
What is the Perl way of removing a directory and then all empty parent directories up to the first non-empty one? In other words, what could one use instead of:
system 'rmdir', '-p', $directory;
which, in starting with d
, would first remove d
and then c
, and then b
, but not a
, since a
would still contain x
, like this:
a
a/b
a/b/c
a/b/c/d
a/x
resulting in
a
a/x
It's not the built-in rmdir
, as it can only remove a single directory. (doc)
It's not finddepth ( sub {rmdir}, '.' )
, using File::Find
, as it removes children, not parents. (doc)
It's not the remove_tree
function of the File::Path
module either, since it not only removes children directories but files as well. (doc)
Notice, remove_tree
and finddepth
work in the opposite direction of Bash rmdir --parent
.
Upvotes: 2
Views: 1086
Reputation: 385887
use Path::Tiny qw( path );
my $p = path('a/b/c/d');
while (!$p->is_rootdir()) {
if (!rmdir($p)) {
last if $!{ENOTEMPTY};
die("Can't remove \"$p\": $!\n");
}
$p = $p->parent;
}
Notes:
Efficient. By checking the result of rmdir
instead of using ->children
or ->iterator
, this solution is avoids needless calls to readdir
.
No race conditions. Unlike the solutions that use readdir
(via ->children
or ->iterator
), this solution doesn't suffer from a race condition.
This solution also avoids the redundant -d
check used by an earlier solution.
This solution, unlike those before it, will handle a tree that's empty except for the directories to remove.
Upvotes: 5
Reputation: 164859
AFAIK this doesn't exist. You can write it yourself fairly easily with Path::Tiny. It's a simple recursive function.
#!/usr/bin/env perl
use strict;
use warnings;
use v5.10;
use Carp;
use Path::Tiny;
use autodie;
sub Path::Tiny::rmdir_if_empty {
my $self = shift;
# Stop when we reach the parent.
# You can't rmdir('.') anyway.
return if $self eq '.';
croak "$self is not a directory" if !$self->is_dir;
# Stop if the directory contains anything.
# I use an iterator to avoid a possibly very long list.
my $iter = $self->iterator;
return if defined $iter->();
# rmdir will not delete a non-empty directory, a second safeguard
rmdir $self;
return $self->parent->rmdir_if_empty;
}
path("a/b/c/d")->rmdir_if_empty;
Upvotes: 4
Reputation: 63922
As always, using my favorite Path::Tiny
use 5.014;
use warnings;
use Path::Tiny;
use autodie;
my $p = path('a/b/c/d'); # starting
die "$p is not a dir" unless -d $p;
while( ! $p->children ) { # if it is empty
rmdir $p; # remove it
$p = $p->parent; # go upward
}
Upvotes: 3