How can I exclude directories from grep -R?
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
The --exclude-dir flag is the direct answer. Pass it to grep -R once per directory you want to skip, and grep will not descend into those directories at all. This avoids both noisy output and wasted I/O on irrelevant trees like .git, node_modules, or dist.
That single change turns a sluggish, cluttered search into a fast, focused one. The rest of this article covers every variation you will encounter in practice: multiple directories, glob patterns, the find -prune fallback, performance implications, and how modern alternatives like ripgrep handle the same problem.
Excluding a Single Directory
The simplest form takes one directory name:
This tells GNU grep to skip any directory named .git at every level of the tree. The match is on the directory name, not the full path, so a nested .git inside a submodule would also be excluded.
The flag works with both -R (follows symlinks into directories) and -r (does not follow symlinks). Choose based on whether your tree contains symlinks you want to follow.
Excluding Multiple Directories
Repeat --exclude-dir for each directory you want to skip:
This is the most portable and readable approach. Each exclusion is explicit, which makes the command self-documenting in scripts and CI configurations.
Brace Expansion Shorthand
If you are running the command interactively in Bash or Zsh, brace expansion provides a more concise syntax:
The shell expands this into separate --exclude-dir flags before grep sees the arguments. This is convenient at the terminal but has a portability caveat: brace expansion is a shell feature, not a POSIX guarantee. In sh, dash, or certain CI environments, it may not expand at all and will be passed as a literal string.
Using Glob Patterns for Directory Names
The --exclude-dir flag accepts shell-style globs, which is useful when excluded directories follow a naming pattern:
This skips directories like venv, venv311, .cache, __pycache__, and similar. Quote the pattern to prevent the shell from expanding it before grep processes it.
You can combine literal names and patterns in the same command:
The '.*' pattern excludes all hidden directories (those starting with a dot), which is a common shortcut for skipping .git, .svn, .idea, and similar directories at once.
Excluding Files as Well as Directories
Grep also supports --exclude for individual files and --include for restricting to specific file types. These compose naturally with --exclude-dir:
This searches only .js and .ts files, skips minified and source map files, and avoids node_modules and .git entirely.
| Flag | Scope | Accepts Globs | Example |
--exclude-dir | Directories | Yes | --exclude-dir='build*' |
--exclude | Files | Yes | --exclude='*.log' |
--include | Files (whitelist) | Yes | --include='*.py' |
The find -prune Fallback
When exclusion rules become more complex than --exclude-dir can handle, use find to build the file list and pipe it to grep:
This approach gives you full access to find predicates: depth limits, modification time filters, permission checks, and arbitrary boolean logic. The -print0 and xargs -0 pair handles filenames with spaces, quotes, or newlines safely.
A practical example with depth limiting:
This limits the search to three levels deep and skips any vendor directory, which is useful in monorepos where you want to search top-level code without descending into vendored dependencies.
Making Exclusions Permanent with GREP_OPTIONS and Aliases
If you always want to exclude certain directories, set up a shell alias instead of typing the flags every time:
Then use it like:
Avoid setting the GREP_OPTIONS environment variable for this purpose. It was deprecated in GNU grep 2.21 because it affects every grep invocation, including those inside scripts that do not expect the extra flags. An alias is scoped to your interactive shell, which is safer.
Performance Comparison
Excluding directories is not just a convenience; it has a measurable impact on search speed. Here is a representative comparison on a medium-sized JavaScript project (roughly 50,000 files including node_modules):
| Command | Directories Traversed | Approximate Time |
grep -R "pattern" . | All (including node_modules) | 12.4 seconds |
grep -R --exclude-dir=node_modules "pattern" . | All except node_modules | 0.8 seconds |
rg "pattern" | Respects .gitignore automatically | 0.3 seconds |
The improvement comes from skipping the directory traversal entirely. Grep does not open or stat files inside excluded directories. In projects where node_modules contains tens of thousands of files, this reduces search time by an order of magnitude or more.
Modern Alternatives
If you search code regularly, tools like ripgrep (rg), ag (The Silver Searcher), or ack are worth considering. They automatically respect .gitignore rules, skip binary files, and are faster than GNU grep on large trees:
These tools are not always available on production servers or in minimal Docker images, so knowing the grep --exclude-dir approach remains essential.
Common Pitfalls
Filtering output instead of excluding traversal. Piping grep output through another grep to remove unwanted paths (grep -R "pattern" . | grep -v node_modules) does not stop grep from reading those files. The directory is still traversed, so you waste time and I/O. Always exclude at the source.
Relying on brace expansion in portable scripts. A command like --exclude-dir={a,b,c} works in Bash and Zsh but silently fails in sh or dash. In scripts, use separate --exclude-dir flags for each directory.
Forgetting -print0 with find and xargs. Without null-delimited output, filenames containing spaces or special characters break the pipeline. Always pair find -print0 with xargs -0.
Confusing directory names with paths. The --exclude-dir=node_modules flag matches any directory named node_modules anywhere in the tree, not a specific path. If you need to exclude only ./src/node_modules but not ./other/node_modules, the find -prune approach with explicit path matching is necessary.
Mixing up -r and -R. On GNU grep, -R follows symbolic links into directories while -r does not. If your project uses symlinks and you are getting unexpected results, check which flag you are using.
Summary
- The direct answer is
grep -R --exclude-dir=DIR "pattern" PATHusing GNU grep. - Repeat
--exclude-dirfor multiple directories. Brace expansion is concise but shell-dependent. - Glob patterns work inside
--exclude-dirfor families of directory names. - Combine
--exclude-dirwith--excludeand--includeto filter both directories and file types. - For complex exclusion logic, use
find -prunepiped toxargs grep. - Shell aliases are the safe way to make exclusions persistent. Avoid
GREP_OPTIONS. - Excluding directories before traversal saves significant time compared to filtering output afterward.
- For regular code searches,
ripgreporaghandle exclusions automatically via.gitignore.
.png&w=3840&q=75)
Tackling System Design Interview Problems
A short course that equips you with the skills to approach system design interviews methodically.
Start the free courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.