grep command
excluding directories
command line tools
Linux commands
coding tips

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.

Browse interview questions

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.

bash
grep -R --exclude-dir=node_modules --exclude-dir=.git "TODO" .

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:

bash
grep -R --exclude-dir=.git "pattern" /path/to/project

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:

bash
1grep -R \
2  --exclude-dir=.git \
3  --exclude-dir=node_modules \
4  --exclude-dir=dist \
5  --exclude-dir=coverage \
6  --exclude-dir=__pycache__ \
7  -n "FIXME" .

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:

bash
grep -R --exclude-dir={.git,node_modules,dist,build,coverage} "FIXME" .

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:

bash
grep -R --exclude-dir='venv*' --exclude-dir='.*cache*' "import" .

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:

bash
1grep -R \
2  --exclude-dir=node_modules \
3  --exclude-dir='venv*' \
4  --exclude-dir='.*' \
5  -n "config" .

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:

bash
1grep -R \
2  --exclude-dir=node_modules \
3  --exclude-dir=.git \
4  --exclude='*.min.js' \
5  --exclude='*.map' \
6  --include='*.js' \
7  --include='*.ts' \
8  -n "addEventListener" .

This searches only .js and .ts files, skips minified and source map files, and avoids node_modules and .git entirely.

FlagScopeAccepts GlobsExample
--exclude-dirDirectoriesYes--exclude-dir='build*'
--excludeFilesYes--exclude='*.log'
--includeFiles (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:

bash
1find . \
2  -type d \( -name .git -o -name node_modules -o -name dist \) -prune \
3  -o -type f -name '*.py' -print0 \
4| xargs -0 grep -n "def "

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:

bash
1find . -maxdepth 3 \
2  -type d -name vendor -prune \
3  -o -type f -print0 \
4| xargs -0 grep -l "LICENSE"

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:

bash
# In ~/.bashrc or ~/.zshrc
alias grepr='grep -R --exclude-dir=.git --exclude-dir=node_modules --exclude-dir=dist -n'

Then use it like:

bash
grepr "TODO" .

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

CommandDirectories TraversedApproximate Time
grep -R "pattern" .All (including node_modules)12.4 seconds
grep -R --exclude-dir=node_modules "pattern" .All except node_modules0.8 seconds
rg "pattern"Respects .gitignore automatically0.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:

bash
1# ripgrep: automatically skips .git, node_modules (if in .gitignore), binaries
2rg "pattern"
3
4# Explicitly skip a directory in ripgrep
5rg --glob '!vendor/' "pattern"

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" PATH using GNU grep.
  • Repeat --exclude-dir for multiple directories. Brace expansion is concise but shell-dependent.
  • Glob patterns work inside --exclude-dir for families of directory names.
  • Combine --exclude-dir with --exclude and --include to filter both directories and file types.
  • For complex exclusion logic, use find -prune piped to xargs 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, ripgrep or ag handle exclusions automatically via .gitignore.

Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track 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.

Browse interview questions