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.
GNU find's -delete always implies -depth for the whole traversal, 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 the whole traversal. | 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 | Yes (always, since the action was introduced) | 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
FAQ
No. find -delete deletes every match silently with no prompt, no progress bar, and no undo. The only safety pattern is to run the same command with -print first, eyeball the list, and then change -print to -delete. For interactive confirmation per file, use find ... -exec rm -i \; instead.
Usually not. find -delete calls unlink() on each file, which removes the directory entry. On ext4, btrfs, and most modern filesystems, the inode and data blocks are freed and may be overwritten at any time. Recovery tools like extundelete or photorec can sometimes pull back recently-deleted files, but only if the underlying blocks haven't been reused.
The reliable safety net is backups, not recovery. If you're about to run a destructive cleanup on a server, snapshot first if your filesystem (ZFS, btrfs) or storage layer (cloud disk snapshot) supports it.
rm -rf /path deletes everything under /path unconditionally. find /path -delete deletes matched files only, where "matched" is defined by the find expression (name, size, time, type, etc.). For "remove this whole directory tree" use rm -rf; for "remove only the files that meet these criteria" use find ... -delete.
rm -rf is also significantly faster on a large unconditional delete because it doesn't have to evaluate each file against an expression. find is faster than rm -rf only when the filter actually eliminates most of the tree.
This happens when find tries to delete a directory before its contents. The fix is to add -depth to force depth-first traversal: find . -depth -type d -empty -delete. GNU find's -delete always implies -depth, but being explicit is safer for cross-platform scripts. The typical recipe is two passes: first delete the files (find ... -type f ... -delete), then delete the now-empty directories (find ... -depth -type d -empty -delete).
Slightly. find -delete handles weird filenames (spaces, newlines, quotes) safely because find never passes the filename to the shell. xargs rm needs the NUL-delimited form (find -print0 | xargs -0 rm) to be equally safe; the unsafe find | xargs rm form breaks on filenames containing whitespace or newlines.
For speed, both are comparable when xargs uses -0 and batches arguments. The differences are operational: find -delete is shorter to type; xargs rm lets you add flags like -v (verbose) or -f (force, no prompts).
Filter with -type f: find /path -type f -delete removes all regular files but leaves directories in place (even empty ones). If you want to also remove now-empty directories, run a second pass: find /path -depth -type d -empty -delete. The order matters; do files first.
Sources
Authoritative references this article was fact-checked against.
- GNU project documentationgnu.org





