How can I grep recursively, but only in files with certain extensions?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Use grep -r --include="*.ext" "pattern" /path to search recursively but only in files matching a specific extension. The --include flag tells grep to examine only files whose names match the given glob pattern, skipping everything else. This is the simplest and most efficient approach for extension-filtered recursive searches.
The --include Flag: The Direct Solution
The -r flag (or --recursive) tells grep to descend into subdirectories. The --include flag filters which files to search. Files that do not match the pattern are never opened, making this faster than grepping everything and filtering afterward.
Combining --include with --exclude-dir
In real projects, you almost always want to skip certain directories like node_modules, .git, or build output:
Without --exclude-dir, grep will descend into node_modules (which can contain hundreds of thousands of files) and produce both slow performance and irrelevant results.
Alternative: Using find with grep
The find + grep combination gives you more control over file selection, especially when you need criteria beyond just the extension (file size, modification time, permissions):
The -exec ... {} + syntax passes multiple filenames to grep at once (like xargs), which is faster than -exec ... {} \; that runs grep once per file.
Handling Filenames with Spaces
If your project contains filenames with spaces or special characters, use null-delimited output:
The -print0 and -0 flags use null bytes as delimiters instead of newlines, correctly handling filenames like my file (copy).md.
Modern Alternatives: ripgrep, ag, and fd
For large codebases, specialized search tools are significantly faster than grep because they respect .gitignore, skip binary files, and use parallel processing:
ripgrep (rg)
ripgrep is typically 2-10x faster than grep on large repositories.
The Silver Searcher (ag)
fd + grep (modern find replacement)
Comparison of Approaches
| Approach | Syntax | Respects .gitignore | Speed on Large Repos | Handles Spaces |
grep -r --include | grep -r --include="*.py" "pat" . | No | Moderate | Yes |
find + grep | find . -name "*.py" -exec grep "pat" {} + | No | Moderate | With -print0 |
ripgrep (rg) | rg -t py "pat" | Yes | Fast | Yes |
ag | ag --python "pat" | Yes | Fast | Yes |
fd + grep | fd -e py | xargs grep "pat" | Yes | Fast | With -print0 |
Useful grep Flags for Code Search
These flags combine well with --include for practical code searching:
Searching for Multiple Patterns
Creating a Shell Alias for Common Searches
If you search code frequently with the same extensions, create aliases:
Common Pitfalls
Forgetting to quote the glob pattern. Without quotes, the shell expands *.py against files in the current directory before grep sees it. Always write --include="*.py" with quotes.
Using -r vs -R. On GNU grep, -r does not follow symlinks while -R does. On macOS/BSD grep, -r follows symlinks. If your project has symlinked directories (common in monorepos), this distinction matters. Use -R explicitly if you want to follow symlinks.
Mixing up --include and --exclude order. grep processes --include and --exclude in the order given. If you write --exclude="*.test.ts" --include="*.ts", the include re-adds what the exclude removed. Put --include first, then --exclude.
Not excluding build directories. Searching without --exclude-dir in a JavaScript project scans node_modules, which can contain 50,000+ files. Always exclude node_modules, .git, build, dist, and other generated directories.
Expecting recursive search by default. Plain grep "pattern" . does not recurse. You must pass -r or -R. Omitting the flag searches only the specified path (and fails on directories with "Is a directory" error).
macOS grep limitations. macOS ships BSD grep, which lacks -P (Perl regex). For advanced regex patterns on macOS, install GNU grep via brew install grep (available as ggrep) or use ripgrep.
Summary
grep -r --include="*.ext" "pattern" . is the standard way to search recursively within specific file types. Combine it with --exclude-dir to skip generated directories, and add -n, -l, or -c flags depending on whether you need line numbers, filenames only, or match counts. For large codebases, consider ripgrep (rg) as a faster drop-in replacement that automatically respects .gitignore and skips binary files. Create shell aliases for your most common search patterns to save time on repeated lookups.

