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

— LiveStream

Clean up Files & Folders Scripts

Automated file and folder cleanup scripts delete files older than a set number of days, then remove the empty folders left behind — the single most reliable way to stop logs, temp data, and screenshots from silently filling a disk. This guide gives you correct, production-ready cleanup scripts for both Windows (PowerShell and forfiles) and Linux (a find one-liner and a robust Bash script), plus a modernized Perl version, scheduling, common pitfalls, and how to verify the cleanup actually worked.

The problem: disks fill up with old files nobody deletes

Every server accumulates files that were useful once and are now just dead weight: rotated logs, report exports, backup dumps, browser screenshots, and temporary upload staging folders. Left alone they grow until a partition hits 100%, and then services that need to write — databases, web servers, mail queues — start failing in ways that look unrelated to "the disk is full".

The fix is a scheduled file and folder cleanup script that does two jobs in one pass: delete files older than a retention threshold (say 10, 21, or 30 days), and then prune any directories that are now empty. The scripts below do exactly that, are safe to schedule, and are corrected versions of patterns that are widely (and often buggily) copy-pasted around the web.

The solution at a glance

You only need to choose a retention period and a target folder. The core logic is identical across platforms:

  • Find old files by modification time and delete them.
  • Recurse into every subfolder so nested files are caught.
  • Remove empty folders afterwards, working from the deepest level up.
  • Log everything so you have an audit trail of what was removed.

The single most important rule before you run any of these: always dry-run first. Print what would be deleted, eyeball it, and only then enable the actual delete. A cleanup script with the wrong path can wipe a production share in seconds.

Linux file and folder cleanup (the modern, recommended way)

On Linux, find already does almost everything, so you rarely need a long script. Start with a read-only preview.

Step 1 — Preview what will be deleted

  1. Pick the target directory and retention in days (here, 21).
  2. List matching files without deleting anything:

find /var/log/myapp -type f -mtime +21 -print

-mtime +21 means "modified more than 21 full 24-hour periods ago." Read the output carefully — this is your safety check.

Step 2 — Delete old files, then prune empty folders

  1. Delete the files (use -delete, which is safe and fast):

find /var/log/myapp -type f -mtime +21 -delete

  1. Remove directories left empty by the deletion. The -depth flag is essential — it processes children before parents so nested empties collapse correctly:

find /var/log/myapp -mindepth 1 -depth -type d -empty -delete

-mindepth 1 protects the top-level folder itself from being removed even if it ends up empty.

Step 3 — A reusable, logged Bash script

For scheduling, wrap it in a script that takes arguments and writes a dated log — the safe, expanded equivalent of the original cleanup tooling:

#!/usr/bin/env bash
set -euo pipefail
SRC="${1:?Usage: cleanup.sh <dir> <days> <logname>}"
DAYS="${2:?days required}"
NAME="${3:-cleanup}"
LOGDIR="/var/log/cleanup"; mkdir -p "$LOGDIR"
LOG="$LOGDIR/${NAME}-$(date +%F).log"
echo "[$(date '+%F %T')] start: $SRC older than ${DAYS}d" >> "$LOG"
find "$SRC" -type f -mtime +"$DAYS" -print -delete >> "$LOG"
find "$SRC" -mindepth 1 -depth -type d -empty -delete >> "$LOG"
echo "[$(date '+%F %T')] done" >> "$LOG"

Run it: ./cleanup.sh /var/log/myapp 21 myapp. The set -euo pipefail line makes the script abort on errors instead of silently continuing — a critical safety improvement over the loose original scripts.

Windows file and folder cleanup with PowerShell (preferred)

On modern Windows, PowerShell is the recommended tool — it is more readable, safer, and far easier to test than legacy batch. The old forfiles.exe approach still works and is shown afterward for compatibility.

Step 1 — Preview with -WhatIf

PowerShell's -WhatIf switch is the gold standard for a dry run: it prints every action without performing it.

  1. Open PowerShell and set the cutoff date and target:

$Target = "D:\Reports"; $Days = 21
$Cutoff = (Get-Date).AddDays(-$Days)
Get-ChildItem -Path $Target -Recurse -File |
  Where-Object { $_.LastWriteTime -lt $Cutoff } |
  Remove-Item -WhatIf

Step 2 — Delete old files for real, with a log

  1. Drop -WhatIf and capture what was removed:

$Log = "C:\Logs\cleanup-$(Get-Date -f yyyy-MM-dd).log"
Get-ChildItem -Path $Target -Recurse -File |
  Where-Object { $_.LastWriteTime -lt $Cutoff } |
  ForEach-Object { "$(Get-Date -f s) DEL $($_.FullName)" | Add-Content $Log; Remove-Item $_.FullName -Force }

Step 3 — Remove the empty folders left behind

Sort directories by path length descending so the deepest are deleted first, mirroring Linux's -depth behavior:

Get-ChildItem -Path $Target -Recurse -Directory |
  Sort-Object { $_.FullName.Length } -Descending |
  Where-Object { -not (Get-ChildItem $_.FullName -Recurse -Force) } |
  Remove-Item -Force

Windows cleanup with forfiles (legacy / batch compatibility)

If you must stay in classic batch (for example, an old scheduled task you can't rewrite yet), forfiles.exe deletes files by age. Here is a corrected version of the common batch pattern — the originals floating around often have broken paths and a missing space that silently skips a folder:

@echo off
set "ROOT=D:\Destination"
echo [%DATE% %TIME%] Start
forfiles /p "%ROOT%\x" /s /m *.* /d -10 /c "cmd /c del @file"
forfiles /p "%ROOT%\y" /s /m *.* /d -21 /c "cmd /c del @file"
forfiles /p "%ROOT%\z" /s /m *.* /d -21 /c "cmd /c del @file"
echo [%DATE% %TIME%] Done

Key corrections versus the typical copy-paste:

  • Quote every path ("%ROOT%\x") so spaces don't break the command.
  • Mind the space after the path...\Y/s with no space (a real bug in many shared scripts) makes forfiles treat the slash as part of the path and skip the folder entirely.
  • /d -10 means "older than 10 days"; /s recurses subfolders.
  • forfiles deletes files only — pair it with a for /d ... rmdir pass to clear empty folders.

Deleting just the single oldest file

A common variant keeps a folder capped by removing only the oldest item (useful for a screenshot folder you want trimmed gradually):

@echo off
setlocal
set "Folder=E:\Screenshots"
set "Mask=*.png"
set "Oldest="
for /f "delims=" %%a in ('dir /b /a:-d /o:d "%Folder%\%Mask%" 2^>nul') do (
  set "Oldest=%%a"
  goto :done
)
:done
if defined Oldest ( del "%Folder%\%Oldest%" ) else ( echo No files match %Mask% )

The /a:-d flag (corrected from the original) excludes directories so you never accidentally target a subfolder, and /o:d sorts oldest-first.

The Perl approach, modernized

The classic cross-platform cleanup script was written in Perl, recursing a tree, deleting files past a day limit, and removing empty folders. Perl still works, but a few fixes make it safe and idiomatic. Use File::Find instead of hand-rolled chdir recursion, and never trust globbed . / .. entries:

#!/usr/bin/perl
use strict; use warnings;
use File::Find;
my ($dir, $days) = @ARGV;
die "Usage: cleanup.pl <dir> <days>\n" unless $dir && defined $days;
find(sub {
  return unless -f $_;
  unlink $_ if -M $_ > $days;
}, $dir);
finddepth(sub {
  rmdir $_ if -d $_;  # rmdir only succeeds on empty dirs
}, $dir);

Notes on the corrections: -M returns the file's age in days as a floating-point number relative to script start, so -M $_ > $days is the correct "older than" test. finddepth visits the deepest directories first, and rmdir harmlessly fails (and is skipped) on any folder that still contains files — exactly the behavior you want.

Scheduling the cleanup so it runs itself

A cleanup script only earns its keep when it runs on a schedule. Below are the two standard mechanisms.

PlatformToolExample (daily at 02:00)
Linuxcron0 2 * * * /opt/scripts/cleanup.sh /var/log/myapp 21 myapp
Linux (systemd)systemd timerAn OnCalendar=*-*-* 02:00:00 timer calling a oneshot service
WindowsTask Schedulerschtasks /create /tn Cleanup /tr "powershell -File C:\scripts\cleanup.ps1" /sc daily /st 02:00

Edit the Linux crontab with crontab -e. On Windows, run the schtasks command from an elevated prompt, or use the Task Scheduler GUI and point the action at powershell.exe with your script as the argument. Always give scheduled tasks an account that has permission to the target folder but not more than it needs.

Common pitfalls (and how to avoid them)

  • Modification time vs. access time. find -mtime and PowerShell's LastWriteTime use modification time. If files are touched but not changed, they may look "fresh." Compare $_.CreationTime in PowerShell instead only when creation date genuinely matches your retention intent.
  • The off-by-one in -mtime. -mtime +21 means strictly more than 21 full days, so a file exactly 21 days old is not matched. Account for this when retention boundaries matter.
  • Deleting the top folder by accident. Always use -mindepth 1 (Linux) or skip the root in PowerShell so the script never removes the directory you pointed it at.
  • Unquoted paths with spaces. The number-one batch and shell bug. Quote every path variable.
  • The missing-space forfiles trap. ...\Folder/s instead of ...\Folder /s silently skips that folder — review each line.
  • No log, no dry run. Never schedule a destructive script you have not first run with -print / -WhatIf, and always write a dated log so you can prove what was removed.
  • Symlinks. By default find does not follow symlinks (good). Do not add -L unless you genuinely want deletions to cross into linked targets.

Verification: confirm the cleanup worked

After the first scheduled run, prove it did what you expected:

  1. Check the log file the script wrote — every deleted path should be listed with a timestamp.
  2. Re-run the preview (find ... -print or Remove-Item -WhatIf). It should now return nothing, confirming no eligible files remain.
  3. Confirm reclaimed space: df -h /var/log/myapp on Linux, or Get-PSDrive D on Windows, before and after.
  4. Spot-check folders: empty directories should be gone, and folders with recent files must still exist intact.
  5. Confirm the schedule fired: systemctl list-timers or grep CRON /var/log/syslog on Linux, or Task Scheduler's "Last Run Result" (0x0 means success) on Windows.

Key Takeaways

  • Two passes, every time: delete old files first, then prune the empty folders they leave — process the deepest folders first.
  • Prefer the modern tool: find -delete on Linux and PowerShell with -WhatIf on Windows are safer and clearer than legacy forfiles batch.
  • Dry-run before you delete: -print / -WhatIf is non-negotiable, and always write a dated log.
  • Protect the root with -mindepth 1 and quote every path to avoid the classic batch bugs.
  • Schedule and verify: wire it into cron, a systemd timer, or Task Scheduler, then confirm logs, reclaimed space, and the next-run timer.

Frequently Asked Questions

How do I delete files older than 30 days in Linux?

Use find /path -type f -mtime +30 -delete. Preview first with -print instead of -delete. To then remove the empty folders left behind, run find /path -mindepth 1 -depth -type d -empty -delete.

How do I delete old files in Windows using PowerShell?

Set a cutoff date and filter by LastWriteTime: Get-ChildItem C:\path -Recurse -File | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } | Remove-Item -WhatIf. Remove -WhatIf once the preview looks correct.

How can I automatically remove empty folders after deleting files?

On Linux use find /path -mindepth 1 -depth -type d -empty -delete. On Windows PowerShell, list directories, sort by path length descending, keep those with no remaining children, and pipe them to Remove-Item — this clears nested empties from the bottom up.

Is forfiles still safe to use for cleanup?

Yes, forfiles.exe still ships with Windows and works for age-based deletion, but PowerShell is preferred because it offers a true -WhatIf dry run, cleaner logging, and easier empty-folder handling. If you keep forfiles, quote all paths and watch for the missing-space bug after a path.

If these scripts saved your disk, subscribe to @explorenystream on YouTube for more practical sysadmin and automation walkthroughs.