Как удалить каталог и его пустых предков с помощью Perl?

Как Perl удаляет каталог, а затем все пустые родительские каталоги до первого непустого? Другими словами, что можно использовать вместо:

system 'rmdir', '-p', $directory;

который, начиная с d, сначала удалили d а потом c, а потом b, но нет a, поскольку a будет по-прежнему содержать x, как это:

a
a/b
a/b/c
a/b/c/d
a/x

в результате чего

a
a/x

Это не встроенный rmdir, так как он может удалить только один каталог. ( док)

Это не finddepth ( sub {rmdir}, '.' ), с помощью File::Findкак убирает детей, а не родителей. ( док)

Это не remove_tree функция File::Path Модуль также, так как он не только удаляет дочерние каталоги, но и файлы. ( док)

Обратите внимание, remove_tree а также finddepth работать в обратном направлении Баш rmdir --parent,

3 ответа

Решение
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;
}

Заметки:

  • Эффективное. Проверяя результат rmdir Вместо того, чтобы использовать ->children или же ->iteratorЭто решение позволяет избежать ненужных звонков readdir,

  • Нет гоночных условий. В отличие от решений, которые используют readdir (с помощью ->children или же ->iterator), это решение не страдает от состояния гонки.

  • Это решение также позволяет избежать лишних -d проверьте, использованный ранее решением.

  • Это решение, в отличие от предыдущих, будет обрабатывать пустое дерево, за исключением каталогов, которые нужно удалить.

AFAIK это не существует. Вы можете написать это довольно легко с помощью Path:: Tiny. Это простая рекурсивная функция.

#!/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;

Как всегда, используя мой любимый путь:: 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
}
Другие вопросы по тегам