/flags/find/find-prune-exclude-directory
find -prune: exclude a directory such as node_modules or .git
- tool
- find
- command
- find
- flag
- -prune
- default
- off (find descends into every directory it can read)
find . -name node_modules -prune -o -type f -name '*.js' -print finds JavaScript files but never enters any node_modules directory. -prune is an action that is always true and tells find not to descend into the current directory. On its own it does nothing visible; it is used on the left of -o (or): when the left side matches a directory, find prunes it and skips the right side; for everything else the left side is false and the right side runs.
With and without the explicit -print
mkdir -p proj/src proj/node_modules/pkg proj/.git
touch proj/a.txt proj/src/b.txt proj/node_modules/pkg/d.txt proj/.git/e.txt
echo '-- no prune'; find proj -type f | sort
echo '-- prune, explicit -print'; find proj \( -name node_modules -o -name .git \) -prune -o -type f -print | sort
echo '-- prune, no -print'; find proj -name node_modules -prune -o -type f | sortOutput
-- no prune proj/.git/e.txt proj/a.txt proj/node_modules/pkg/d.txt proj/src/b.txt -- prune, explicit -print proj/a.txt proj/src/b.txt -- prune, no -print proj/.git/e.txt proj/a.txt proj/node_modules proj/src/b.txt
The last command lists proj/node_modules itself: with no action in the expression, find adds an implicit -print around the whole expression, and the pruned branch is true. Always end the other branch with -print (or -exec, -print0).
Patterns
| Goal | Expression |
|---|---|
| Skip every directory named X | -name X -prune -o ... -print |
| Skip one specific path | -path ./build -prune -o ... -print |
| Skip several names | \( -name .git -o -name node_modules \) -prune -o ... -print |
| Skip hidden directories | -name '.?*' -type d -prune -o ... -print |
| Portable -maxdepth 1 | ! -name . -prune (POSIX find, with . as start) |
-path matches the whole path as find prints it, so the pattern must start the same way as the starting point: with find . use -path ./build, with find proj use -path proj/build. A pattern that never matches silently prunes nothing:
mkdir -p tree/build tree/src
touch tree/build/out.o tree/src/main.c
echo '-- wrong prefix'; find tree -path ./build -prune -o -type f -print | sort
echo '-- right prefix'; find tree -path tree/build -prune -o -type f -print | sortOutput
-- wrong prefix tree/build/out.o tree/src/main.c -- right prefix tree/src/main.c
-prune or -not -path
find . -type f -not -path '*/node_modules/*' gives similar output, but find still walks every file under node_modules and then filters them out. -prune never opens the directory, which matters on trees with hundreds of thousands of files. To limit depth rather than skip names, use find -maxdepth.
Pitfalls
- Parentheses must be escaped (
\(\)) or quoted and separated by spaces; otherwise the shell interprets them, or find reports paths must precede expression. -prunehas no effect when-depthis active, and-deleteimplies-depth. To delete files outside a directory, prune and then use-exec rminstead.-namepatterns must be quoted ('.?*') so the shell does not expand them before find sees them.- grep has its own way to skip directories while searching contents: grep --exclude-dir.
FAQ
Why does the pruned directory still appear in the output?
Because the command has no explicit -print. Without one, find prints everything for which the whole expression is true, and -prune returns true for the pruned directory. Add -print to the other branch.
How do I exclude several directories?
Group the names with escaped parentheses, find . \( -name .git -o -name node_modules \) -prune -o -type f -print. Spaces around the parentheses are required.
Does -prune work with -delete or -depth?
No. -depth (which -delete turns on) processes a directory after its contents, so there is nothing left to skip and -prune has no effect. GNU find documents this.