jkeenan1 has asked for the wisdom of the Perl Monks concerning the following question:

The documentation to File::Path suggests that the rmtree() function rolls back the result of that same module's mkpath() function.

Similarly, the "rmtree" function provides a convenient way to delete a subtree from the directory structure, much like the Unix command "rm -r". "rmtree" takes three arguments:

o the root of the subtree to delete, or a reference to a list of roots. All of the files and directories below each root, as well as the roots themselves, will be deleted.

As I interpret this documentation -- particularly the reference to the "root" of the subtree -- if I create a directory structure alpha/beta/gamma with mkpath, I should expect to find alpha/ and all subdirectories and files beneath it eliminated by using rmtree(). But that's not what I'm getting.

use File::Path; use Test::More qw|no_plan|; my $tmp = "/Users/jimk/tmp"; chdir $tmp or die; my $partial = "alpha/beta/gamma"; mkpath($partial); ok(-d "$tmp/$partial", "$tmp/$partial created"); rmtree($partial); ok(! -d "$tmp/$partial", "$tmp/$partial deleted");

Notwithstanding what ok() is reporting, only the lowest level subdirectory, gamma, is being deleted. But what I really wanted to accomplish was deleting alpha/, alpha/beta/, alpha/beta/gamma/. What am I not getting about rmtree?

And while we're talking about File::Path ... is the / following 'mkpath' and preceding 'foo' an error?

mkpath(['/foo/bar/baz', 'blurfl/quux'], 1, 0711); rmtree(['foo/bar/baz', 'blurfl/quux'], 1, 1);

Shouldn't it simply be ...

mkpath(['foo/bar/baz', 'blurfl/quux'], 1, 0711); rmtree(['foo/bar/baz', 'blurfl/quux'], 1, 1);

Thank you very much.

Jim Keenan

Replies are listed 'Best First'.
Re: File::Path::rmtree: What am I not grokking?
by ikegami (Patriarch) on Nov 02, 2005 at 22:42 UTC

    Here's a (slightly tested) solution:

    use File::Spec (); sub unmkpath { my ($path) = @_; my ($vol, $dirs) = File::Spec->splitpath($path, 1); my @dirs = File::Spec->splitdir($dirs); while (@dirs) { my $dir = File::Spec->catpath($vol, File::Spec->catdir(@dirs), ' +'); rmdir($dir); pop(@dirs); } }

    Portable.
    Handles pathes with volumes.
    Handles absolute paths.
    Handles relative paths.

    It could be optimized a little.

      Nicely thorough. Along the lines of my other reply,

      use File::Spec::Functions qw( splitpath splitdir catpath catdir ); sub unmkpath { my ( $path ) = @_; my ( $vol, @dir ) = do { my ( $vol, $dirs ) = splitpath( $path, 1 ); ( $vol, splitdir $dirs ); }; pop @dir while @dir and rmdir catpath $vol, catdir( @dir ), ''; }

      Update: fixed a couple typos and a minor forgeto.

      Makeshifts last the longest.

Re: File::Path::rmtree: What am I not grokking?
by Fletch (Bishop) on Nov 02, 2005 at 19:27 UTC

    You're telling rmtree to remove the directory tree starting at .../alpha/beta/gama and it's doing exactly what you've told it to do (removed everything under the subdirectory gamma). If you want it to start at .../alpha and remove everything under it then do so.

    Addendum: And as for the slash question, it depends on whether you want the new directory tree created under the root directory / or under the current working directory.

Re: File::Path::rmtree: What am I not grokking?
by ikegami (Patriarch) on Nov 02, 2005 at 20:41 UTC

    In a few words,
    mkpath creates a directory, even if it's parent does not exist.
    rmtree deletes a directory, even if it's not empty.

    rmtree works like the unix commnd rm -r. It recursivly deletes children, not parents. In other words, it deletes a directory, whether it's empty or not. For example, using rmtree to delete /base/dir will delete /base/dir, /base/dir/a, /base/dir/a/1 and /base/dir/b.

    I don't know of anything that does what you want with a single command.

    And while we're talking about File::Path ... is the / following 'mkpath' and preceding 'foo' an error?

    The leading slash makes it an absolute path. If you omit the leading slash, the path is relative to the current directory. Both are valid, but have different meanings.

      rmdir -p on Unices will work that way. If you say rmdir -p foo/bar/baz, it will remove foo/bar/baz, then foo/bar, and finally foo. However, it only works if these directories are all empty (since anything else wouldn’t make sense).

      I suppose this is about equivalent, except it also does an rm -r on the top of the path, so if you give it foo/bar/baz it will remove foo/bar/baz and will then remove foo/bar and foo as well if they turn out empty.

      use File::Path; use File::Spec::Functions qw( splitdir catdir ); sub rmtree_path { my ( $path ) = @_; rmtree $path; my @path = splitdir $path; do { pop @path } while @path and rmdir catdir @path; }

      Untested.

      Makeshifts last the longest.

      I don't know of anything that does what you want with a single command.

      How about this?

      use File::Path; use File::Spec; use Test::More qw|no_plan|; my $tmp = "/Users/jimk/tmp"; chdir $tmp or die; my $partial = "alpha/beta/gamma"; mkpath($partial); ok(-d "$tmp/$partial", "$tmp/$partial created"); rmfromtop($partial); ok(! -d "$tmp/$partial", "$tmp/$partial deleted"); ok(! -d "alpha/beta", "alpha/beta deleted"); ok(! -d "alpha", "alpha deleted"); sub rmfromtop { rmtree((File::Spec->splitdir(+shift))[0]); }

      jimk

        I think you missed the point. It deletes much more than it should. For example, it will delete alpha/important. Also, it doesn't work with absolute paths.

        What’s the point? If you rmtree('alpha') it will recurse into all the subdirs anyway.

        Makeshifts last the longest.