Skip to content

/flags/find/find-prune-exclude-directory

find -prune: exclude a directory such as node_modules or .git

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash) · find (GNU findutils) 4.11.0updated
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

bash
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 | sort

Output

-- 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

GoalExpression
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:

bash
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 | sort

Output

-- 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.
  • -prune has no effect when -depth is active, and -delete implies -depth. To delete files outside a directory, prune and then use -exec rm instead.
  • -name patterns 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.

Sources