DevOps · K8s · Volleyball · Travel  •  DevOps · K8s · Volleyball · Travel  •  DevOps · K8s · Volleyball · Travel
Explore NY Stream

— LiveStream

Remove_Empty_Folders.pl

A Perl script to remove empty folders walks a directory tree from the bottom up and deletes every subfolder that contains no files and no non-empty subfolders, while never touching a single file. This guide gives you a correct, safe, runnable version, explains exactly how the recursion works, fixes the classic bugs that break naive implementations, and shows how to verify the cleanup before and after you run it.

The Problem: Empty Folders That Multiply

Long-lived file systems accumulate empty directories. Backup jobs create dated folder trees and then prune the files but leave the shells behind. Build pipelines, photo imports, and unzip operations scatter hollow folders everywhere. Over time you end up with thousands of empty directories that clutter listings, slow down indexing, and make a tree impossible to read.

The requirement is precise and a little dangerous: delete only folders, and only when they are truly empty. The script must never delete a file, and it must correctly handle a folder that becomes empty only after its empty children are removed. That "becomes empty" case is the heart of the problem and the reason a simple one-line find is not always enough on every platform.

The Solution: Bottom-Up Recursion in Perl

The correct strategy is depth-first, post-order traversal: descend into every subfolder first, delete the empty ones you find at the bottom, and only then test the parent. Because the children are gone, a parent that held nothing but empty folders now qualifies for deletion too. This single rule lets the deletion "bubble up" the tree in one pass.

Perl is an excellent fit for this system administration task because it ships on virtually every Unix and Linux box, handles directories portably, and gives you precise control over what gets deleted. The key built-in is rmdir, which is intentionally safe: it removes a directory only if it is empty and fails otherwise. That means even a buggy script physically cannot delete a folder that still contains a file.

A Corrected, Production-Ready Script

Below is a clean implementation that fixes the common pitfalls of older scripts (duplicate print statements, fragile chdir juggling, and accidental recursion into . and ..). It uses Perl's standard File::Find module, which already implements a correct bottom-up walk and avoids the dot-directory trap entirely.

  1. Save the code as remove_empty_folders.pl.
  2. Make it executable with chmod +x remove_empty_folders.pl (Unix/Linux).
  3. Run a dry run first, then run for real.

The script:

#!/usr/bin/perl
use strict;
use warnings;
use File::Find;

my $dry_run = 0;
if (@ARGV && $ARGV[0] eq '--dry-run') {
    $dry_run = 1;
    shift @ARGV;
}

die "USAGE: $0 [--dry-run] <folder_to_cleanup>\n" if @ARGV != 1;
my $root = $ARGV[0];
die "ERROR: '$root' is not a directory.\n" unless -d $root;

my $removed = 0;

# bottomup ensures children are visited BEFORE their parents,
# so a folder emptied by deletions is then deletable too.
finddepth(
    {
        wanted => sub {
            return unless -d $_;      # directories only
            return if $File::Find::name eq $root; # keep the top folder
            if ($dry_run) {
                # rmdir on a non-empty dir fails harmlessly; test emptiness
                opendir(my $dh, $_) or return;
                my @kids = grep { $_ ne '.' && $_ ne '..' } readdir($dh);
                closedir($dh);
                print "WOULD DELETE: $File::Find::name\n" unless @kids;
            }
            elsif (rmdir $_) {
                print "DELETED: $File::Find::name\n";
                $removed++;
            }
        },
        no_chdir => 0,
    },
    $root
);

print "\nDone. Removed $removed empty folder(s).\n" unless $dry_run;

Run it like this:

  1. Preview only (deletes nothing): perl remove_empty_folders.pl --dry-run /path/to/clean
  2. Perform the cleanup: perl remove_empty_folders.pl /path/to/clean

Why finddepth Is the Right Tool

The original-style scripts that inspire this task hand-roll their own recursion with glob and repeated chdir calls. That works, but it is fragile and easy to get wrong. Perl's File::Find module solves the same problem with two named entry points:

  • find visits each entry top-down (parent before children) — wrong for deletion.
  • finddepth visits each entry bottom-up (children before parent) — exactly what empty-folder pruning needs.

Because finddepth already processes the deepest folders first, by the time the walk reaches a parent, every empty child is already gone. A single rmdir per directory is then enough; there is no need for a fragile "second pass" loop. This is a major simplification over older hand-written versions and removes a whole class of bugs.

Manual Recursion: A Perl Script to Remove Empty Folders Without Modules

If you cannot rely on File::Find (for example a stripped-down embedded Perl), here is a self-contained recursive version that mirrors the bottom-up logic explicitly. It returns true when a folder is empty so the caller knows it can delete it.

#!/usr/bin/perl
use strict;
use warnings;

die "USAGE: $0 <folder>\n" if @ARGV != 1;
my $root = $ARGV[0];
die "ERROR: '$root' is not a directory.\n" unless -d $root;

sub prune {
    my ($dir) = @_;
    opendir(my $dh, $dir) or do {
        warn "Cannot read $dir: $!\n";
        return 0;
    };
    my @entries = grep { $_ ne '.' && $_ ne '..' } readdir($dh);
    closedir($dh);

    my $has_content = 0;
    foreach my $e (@entries) {
        my $path = "$dir/$e";
        if (-d $path && !-l $path) {  # real subdir, not a symlink
            if (prune($path)) {
                rmdir($path) or warn "rmdir $path: $!\n";
                print "DELETED: $path\n" unless -d $path;
                $has_content = 1 if -d $path; # survived = still has content
            }
            else { $has_content = 1; }
        }
        else { $has_content = 1; } # a file or symlink = not empty
    }
    return !$has_content; # true means: this dir is now empty
}

prune($root);
print "Cleanup complete.\n";

Notice the !-l $path guard. Without it, the script could follow a symbolic link that points at a directory and recurse out of the intended tree — one of the most dangerous mistakes in any recursive file-system script.

Common Pitfalls When You Remove Empty Directories

Even a short cleanup script hides several traps. These are the ones that cause real damage in production.

  • Top-down deletion. If you delete folders as you descend, a parent that only contained empty children is tested before its children are removed, so it looks non-empty and survives. Always go bottom-up with finddepth or post-order recursion.
  • Recursing into . and ... A raw directory read returns the current and parent entries. Failing to filter them with grep { $_ ne '.' && $_ ne '..' } can send the script climbing up out of your target folder.
  • Following symlinks. Treating a symlinked directory as a real one lets the walk escape the tree and delete folders you never intended to touch. Skip symlinks with -l.
  • Counting hidden files as nothing. Dotfiles such as .DS_Store, .gitkeep, or Thumbs.db are real files. A folder containing only a .gitkeep is intentionally not empty — rmdir correctly refuses to delete it, which is a feature, not a bug.
  • Ignoring rmdir failures silently. Always check the return value. A failed rmdir usually means the folder is not empty, you lack permission, or the path is in use. Logging $! tells you which.
  • Deleting the top-level folder. Most cleanups should preserve the root you were given and remove only its empty descendants. The corrected script skips $root explicitly.
  • Duplicate or contradictory output. Older scripts printed a "Deleting" message twice and printed it whether or not the rmdir succeeded. Print only after a confirmed deletion so your log reflects reality.

Verification: Confirm the Cleanup Was Correct

Never trust a destructive script blindly. Verify both before and after you run it.

  1. Count empty folders first. On Unix/Linux, list every empty directory under the target without deleting anything: find /path/to/clean -type d -empty. Pipe to wc -l to get a count.
  2. Run the dry run. Execute perl remove_empty_folders.pl --dry-run /path/to/clean and confirm the "WOULD DELETE" list matches the find output above.
  3. Snapshot the file count. Record the total number of files before and after: find /path/to/clean -type f | wc -l. This number must be identical afterwards — the script deletes folders, never files.
  4. Run for real, then re-check. After the live run, repeat find /path/to/clean -type d -empty. It should return nothing (or only folders that became empty due to other processes since you started).
  5. Review the log. Each DELETED: line is one removed folder; the final summary count should equal the number of "WOULD DELETE" lines from your dry run.

For a pure command-line alternative on Linux, the equivalent of this entire Perl script to remove empty folders is a single command: find /path/to/clean -mindepth 1 -type d -empty -delete. The -delete action processes matches depth-first, so it handles the bottom-up case correctly. The Perl version still wins when you need cross-platform behavior, custom logging, dry-run previews, or integration into a larger sysadmin toolkit.

Key Takeaways

  • Always delete bottom-up. Use finddepth or post-order recursion so a parent emptied by its deleted children is itself removed in the same pass.
  • rmdir is your safety net. It deletes a directory only if it is empty, so the script can never destroy a file even if your logic has a bug.
  • Filter ., .., and symlinks. Skipping these prevents the walk from escaping the target tree and deleting unintended folders.
  • Hidden files count. A folder holding only a .gitkeep or .DS_Store is not empty and should not be deleted.
  • Dry-run and verify. Preview deletions, confirm the file count is unchanged afterwards, and re-scan with find -type d -empty to prove the cleanup worked.

Frequently Asked Questions

How do I remove all empty folders recursively in Perl?

Use the standard File::Find module with finddepth, which walks the tree bottom-up. In the callback, skip anything that is not a directory, then call rmdir on each folder. Because rmdir only succeeds on empty directories, empty children are removed first and their newly empty parents are then removed too — all in one pass.

Will the script ever delete my files?

No. The script only ever calls rmdir, which by design removes a directory exclusively when it contains no files and no subdirectories. If a folder holds any file at all, rmdir fails and the folder is left untouched. There is no unlink call anywhere, so files cannot be deleted.

What is the difference between find and finddepth in Perl?

find processes each directory before its contents (top-down), while finddepth processes the contents before the directory (bottom-up). For deleting empty folders you must use finddepth, because a parent can only be confirmed empty after its empty subfolders have already been removed.

Is there a one-line command to do the same thing on Linux?

Yes. Run find /path/to/clean -mindepth 1 -type d -empty -delete. The -delete action is depth-first, so it removes nested empty folders correctly. Use the Perl script when you need a dry-run preview, custom logging, symlink safety, or portability across Windows and Unix.

If you found this walkthrough useful, subscribe to @explorenystream on YouTube for more practical sysadmin and scripting guides.