find /path/to/dir -type f -name '*.log' -delete is the shortest way to delete every matching file with one command. It is also the easiest way to nuke a directory tree you didn't mean to.
There is no confirmation prompt. There is no -i interactive mode like rm -i. There is no trash bin. The only safety net is the human at the keyboard, which means the only safe pattern is: write the command with -print first, eyeball the list, then change -print to -delete. That two-step is the difference between a clean script and a 3am incident.
Set your values
Set your OS, search path, and the name pattern. Every find example below updates with your values.
The safe two-step pattern
Always do this. Every time. No exceptions.
Step 1: Dry run with -print. See exactly what would be deleted:
find :search_path -type f -name ':pattern' -printRead the output. Confirm it's only what you expect. Look for unexpected paths, unexpected file types, anything that wasn't on your mental checklist.
Step 2: Swap -print for -delete. Same command, one word different:
find :search_path -type f -name ':pattern' -deleteThat's it. There's no confirmation, no progress bar, no undo. Once you press Enter, the files are gone.
If you're nervous, run a count first: find ... -print | wc -l to know how many files you're about to remove.
Delete files older than N days
The single most common find-delete pattern: clean up stale files. -mtime +N matches files older than N days; combine with -delete:
# Dry run
find :search_path -type f -mtime +:days -print
# Delete
find :search_path -type f -mtime +:days -deleteTypical use: clearing /tmp/cache, rotating log archives, pruning Docker build cache. See find files modified in the last N days for the full -mtime reference including the off-by-one rules.
Delete files larger than a size
Same shape, different filter:
find :search_path -type f -size +100M -print # dry run
find :search_path -type f -size +100M -deleteUseful for finding and removing oversized core dumps, build artifacts, video files in the wrong directory. See find files larger than a size for the full -size reference.
Delete empty directories (the depth-first trap)
This is where most beginner cleanup scripts fail. To delete empty directories, you need find to descend depth-first so children are removed before parents:
find :search_path -depth -type d -empty -deleteThe -depth flag flips find's default traversal order so that parents are processed after their children. Without -depth, find tries to remove a parent first, sees it's not empty (because the children haven't been processed yet), and skips it. Then it removes the children. The parent is now empty but find has already moved past it.
-delete implies -depth automatically in GNU find when combined with -type d, but being explicit is safer for cross-platform scripts.
Two-pass cleanup: files then empty directories
The standard cleanup recipe: first remove files matching a pattern, then remove any directories that are now empty.
# Pass 1: delete files
find :search_path -type f -name ':pattern' -delete
# Pass 2: delete now-empty directories
find :search_path -depth -type d -empty -deleteThis is the pattern any cleanup script for build artifacts, log rotations, or temp-file pruning should follow.
find -delete vs -exec rm vs xargs rm -f
Three ways to delete matched files, in roughly the order I reach for them:
| Approach | Pros | Cons | When to use |
|---|---|---|---|
find ... -delete | Fastest. Built in. Handles weird filenames safely. | No control over what happens per-file. Implies -depth for directories. | Default for simple deletes |
find ... -exec rm {} + | Same speed as -delete. Works with any tool, not just rm. | Slightly more verbose. | When you want rm -f (force) or rm -v (verbose) |
find ... -print0 | xargs -0 rm -f | Same speed. Works with shell pipelines. | Adds a pipe to NUL-delimited safety: requires -0 on both sides. | When you need shell features around the delete |
find ... -exec rm {} \; | Works everywhere. | Forks one rm per file. Slow on large trees. | Avoid unless the command can only accept one arg at a time |
Detailed behavior of each:
# The four ways to delete
find :search_path -type f -name ':pattern' -delete
find :search_path -type f -name ':pattern' -exec rm {} +
find :search_path -type f -name ':pattern' -print0 | xargs -0 rm -f
find :search_path -type f -name ':pattern' -exec rm {} \;For 99% of cleanup scripts, plain -delete is the right choice. The other forms are useful when you need rm's extra flags (-v, -i) or when the deletion is conditional on something a shell can compute that find can't express.
macOS BSD vs GNU find -delete
Most behavior matches between BSD and GNU, with a few corner cases:
| Feature | GNU find | BSD find (macOS default) |
|---|---|---|
-delete exists | Yes | Yes |
-delete implies -depth for directories | Yes (since GNU find 4.6+) | Yes |
-delete reports per-file errors | Yes | Yes |
| Permission errors silence successful deletes | No (-delete continues) | No (continues) |
| Exit code on partial failure | 1 (some deletes failed) | 1 |
-delete -print together | Works (deletes then prints) | Works |
The single behavior I've been bitten by: find -delete on a directory that contains files you don't have permission to delete will silently leave the files in place but still attempt the directory delete (which then fails). Run a -print first to see what permissions look like, or use find ... -exec rm -f to surface the permission errors explicitly.
Common find -delete mistakes
1. Skipping the -print dry run. This is the #1 cause of "I just nuked my home directory" stories. Always preview before deleting.
2. Forgetting -type f. Without -type f, find evaluates directories against the name filter too. find /tmp -name '*.bak' -delete will try to remove any directory called something.bak, fail because it's not empty, and confuse you with errors. Always anchor with -type f for file-only operations.
3. Running cleanup against / or ~. Even with -name, a typo in the pattern can match more than you expect. Always anchor at a specific subdirectory. find / is essentially never the right path for a -delete operation.
4. Forgetting to quote the pattern. find . -name *.bak -delete lets the shell expand *.bak against the current directory. If there's one file matching, find gets that filename as the pattern (not a glob). If there's none, find gets the literal *.bak and a "paths must precede expression" error. Always quote: -name '*.bak'.
5. Using -exec rm {} \; on huge trees. Each match forks a new rm process. On a tree with 100,000 matches, that's 100,000 forks (slow). Use -exec rm {} + (batches) or -delete (no fork at all).
6. Forgetting that -delete doesn't follow symlinks by default. A symlink matched by -delete removes the symlink, not its target. If you want to delete what symlinks point to, you need -L before the search path or different logic entirely.
7. Running with sudo when you don't need to. Files you own can be deleted without sudo. Running sudo find ... -delete widens the blast radius if the find expression is wrong. Use sudo only when the files genuinely require root, and even then, dry-run first.
8. Not knowing the order of test evaluation. find evaluates expressions left to right. find . -delete -name '*.bak' deletes everything in the tree because -delete runs before the name filter. The correct order is find . -name '*.bak' -delete. Tests first, actions last.
When NOT to use find -delete
Reach for a different approach when:
- You need a confirmation prompt per file. Use
find ... -exec rm -i {} \;(interactiverm). Slow but safe. - You need to log what was deleted. Use
find ... -exec rm -v {} +(verboserm) and redirect to a log file.-deleteitself is silent. - You need to move files to a trash directory rather than delete.
find ... -exec mv {} /path/to/trash/ \;moves matched files. Or usetrash(Linux) ortrash-cli(cross-platform) which talks to the system trash. - You're working on a remote system over a slow link. find runs on the remote side;
-deleteis local to that side. But if you're piping the file list back over SSH to inspect, the round-trips dominate. Run find on the remote. - You need atomic batch deletes (all or nothing). find deletes one file at a time. If the script is interrupted, you get partial results. For atomic behavior, list-then-delete via a temp file:
code
find ... -print0 > /tmp/del-list xargs -0 rm -f < /tmp/del-list
See also
- find Command Cheat Sheet: the full find reference
- Find files modified in the last N days: for the
-mtimefilter that pairs with-delete - Find files larger than a size: for the
-sizefilter - External: GNU find -delete docs, BSD find(1) man page, trash-cli





