Skip to content

/commands/grep/grep-recursive-include

grep -r --include: search only certain file types recursively

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash) · grep (GNU grep) 3.0updated
tool
grep
versions
GNU grep 3.x
os
linux, macos, windows

grep -rn --include='*.js' 'pattern' . searches every .js file below the current directory and prints the file name and line number of each match. --include takes a glob matched against the file name; repeat it for several types, and use --exclude-dir to skip directories such as node_modules.

Example

bash
mkdir -p src/lib node_modules/pkg
echo 'const apiUrl = "/v1";' > src/app.js
echo 'export const apiUrl = "/v2";' > src/lib/config.ts
echo 'apiUrl: /v3' > src/notes.md
echo 'var apiUrl = 1;' > node_modules/pkg/index.js
grep -rn --include='*.js' --include='*.ts' --exclude-dir=node_modules 'apiUrl' . | sort

Output

./src/app.js:1:const apiUrl = "/v1";
./src/lib/config.ts:1:export const apiUrl = "/v2";

Quote the globs: unquoted, the shell may expand *.js against the current directory before grep sees it.

Useful variations

CommandWhat it does
grep -rn 'TODO' srcRecursive search with line numbers
grep -rl 'TODO' srcOnly list the files that match
grep -rL 'license' srcList the files that do not match
grep -rn --exclude='*.min.js' 'x' .Skip files matching a glob
grep -rn --exclude-dir={.git,node_modules} 'x' .Skip several directories (brace expansion is done by bash)
grep -rniw 'id' srcCase-insensitive, whole words only
grep -rI 'x' .Skip binary files

Count matches per file

bash
mkdir -p src
printf 'TODO a\nTODO b\n' > src/a.py
printf 'TODO c\n' > src/b.py
printf 'done\n' > src/c.py
grep -rc --include='*.py' 'TODO' src | sort

Output

src/a.py:2
src/b.py:1
src/c.py:0

-c prints a count for every searched file, including files with 0 matches. Append | grep -v ':0$' to hide them.

Several file types: brace expansion vs a quoted glob

grep's globs have no {a,b} syntax. Unquoted, bash expands --include=*.{js,ts} into two --include options before grep runs, which works; quoted, grep receives the braces literally and nothing matches. The same happens with a glob that contains a directory:

bash
mkdir -p src && echo 'apiUrl' > src/a.js && echo 'apiUrl' > src/b.ts
echo '--- unquoted braces (bash expands them):'
grep -rl --include=*.{js,ts} 'apiUrl' . | sort
echo '--- quoted braces:'
grep -rl --include='*.{js,ts}' 'apiUrl' . ; echo "exit $?"
echo '--- glob with a directory:'
grep -rl --include='src/*.js' 'apiUrl' . ; echo "exit $?"

Output

--- unquoted braces (bash expands them):
./src/a.js
./src/b.ts
--- quoted braces:
exit 1
--- glob with a directory:
exit 1

Exit status 1 means no line was selected, so a script cannot tell a wrong glob from a pattern that is genuinely absent. Check the glob on its own with grep -rl --include='*.js' '' . when a search unexpectedly finds nothing.

Options at a glance

OptionMatches againstNotes
--include=GLOBFile base nameSearch only files matching GLOB; repeat for several globs
--exclude=GLOBFile base nameSkip files matching GLOB; when --include and --exclude both match, the last one given wins
--exclude-dir=GLOBDirectory nameSkip directories during the recursive walk
--exclude-from=FILEFile base nameRead exclude globs from FILE, one per line
-r / -RDirectoriesRecurse; -R also follows every symbolic link

BSD grep on macOS supports -r, --include, --exclude and --exclude-dir with the same meaning. In a Git repository, git grep -n 'pattern' -- '*.js' searches only tracked files and skips node_modules without any option. See also grep --exclude-dir and, for matching several patterns at once, grep multiple patterns.

FAQ

Can --include match a directory path such as src/*.js?

No. --include and --exclude compare the glob with the file name only, so a pattern containing a slash matches nothing. Pass the directory as the search path instead, grep -rn --include='*.js' pattern src.

Does grep -r search the current directory when I give no path?

Yes with GNU grep: -r without a file argument searches the working directory. Older versions and BSD grep may read standard input instead, so pass . explicitly in scripts.

Is --include case-sensitive?

Yes. --include='*.js' does not match APP.JS. Add a second --include='*.JS' when such files exist.

Sources