Filehandles, files, directories

rmdir#

Remove an empty directory.

rmdir asks the operating system to delete the directory named by FILENAME. The directory must be empty — it must contain no entries other than . and ... If FILENAME is omitted, rmdir operates on $_. This is a thin wrapper over the rmdir(2) system call and carries all of its restrictions: you cannot remove a non-empty directory, you cannot remove a directory you lack write permission on the parent of, and on most systems you cannot remove your current working directory.

Synopsis#

rmdir FILENAME
rmdir

What you get back#

1 on success, 0 on failure (with $! set to the errno from the failed system call). Unlike most I/O primitives rmdir does not return undef on failure — it returns the integer 0, so the common test rmdir $dir or die $! still works because 0 is false.

Always check the return value. A failed rmdir is silent otherwise; the next line of code will happily proceed as if the directory had been removed.

rmdir $dir
    or die "rmdir $dir failed: $!";

Global state it touches#

  • $_ — used as FILENAME when no argument is given.

  • $! — set to the errno of the failed rmdir(2) call on failure; left untouched on success. Read it immediately; any intervening system call may overwrite it.

Examples#

Plain call, explicit directory:

rmdir "build/tmp"
    or die "rmdir build/tmp: $!";

Default target. Inside while (readdir ...) or a loop that leaves the name in $_:

for (@empty_dirs) {
    rmdir or warn "rmdir $_: $!";
}

Distinguish “already gone” from a real error. ENOENT means the directory does not exist, which is often fine for cleanup code:

use Errno qw(ENOENT);
unless (rmdir $dir) {
    die "rmdir $dir: $!" unless $! == ENOENT;
}

Remove a directory only after emptying it first:

opendir my $dh, $dir or die "opendir $dir: $!";
while (my $entry = readdir $dh) {
    next if $entry eq '.' or $entry eq '..';
    unlink "$dir/$entry" or die "unlink $dir/$entry: $!";
}
closedir $dh;
rmdir $dir or die "rmdir $dir: $!";

Recursive removal is not rmdir’s job. Use File::Path’s remove_tree — it walks the tree and deletes entries bottom-up:

use File::Path qw(remove_tree);
remove_tree("build", { error => \my $err });
die "remove_tree: @$err" if @$err;

Edge cases#

  • Directory not empty: $! is set to ENOTEMPTY on most Unix systems (some BSDs use EEXIST). This is the single most common rmdir failure. List the directory contents or call remove_tree if recursive deletion is what you actually want.

  • No argument: rmdir with no argument uses $_, not @ARGV and not the current directory. Writing rmdir() with empty parentheses is the same as rmdir with no argument — it still reads $_.

  • Symlink to a directory: rmdir refuses to remove symlinks; it operates on directories only. Use unlink on the symlink itself. $! is typically set to ENOTDIR.

  • Current working directory: On Linux, rmdir "."; usually fails with EBUSY or EINVAL depending on the filesystem. Do not rely on being able to remove the directory you are sitting in; chdir elsewhere first.

  • Permissions: you need write + execute permission on the parent directory, not on the directory being removed. Removing /a/b/c checks /a/b’s mode, not /a/b/c’s.

  • Sticky-bit parent (e.g. /tmp): you also need to own the directory being removed or own the parent; otherwise EPERM.

  • Trailing slash: rmdir "foo/" works and means the same as rmdir "foo".

  • Race with another process: between your opendir/readdir loop and the final rmdir, another process may create a file in the directory. You will then see ENOTEMPTY despite having just emptied it. Retry or fail loudly — do not silently ignore.

Differences from upstream#

Fully compatible with upstream Perl 5.42.

See also#

  • mkdir — create a directory; the inverse operation, with the same FILENAME-or-$_ argument convention

  • unlink — remove files (and symlinks); rmdir removes only empty directories

  • chdir — change the current working directory, often used before rmdir to avoid trying to remove the directory you are in

  • opendir / readdir — list a directory’s contents before emptying and removing it

  • $! — holds the errno of a failed rmdir; check for ENOENT / ENOTEMPTY / EPERM to branch on the reason