grep Recursive Search in Linux: Directories, Include/Exclude, and File Lists

Walk a directory tree with grep -r or -R, narrow results with --include and --exclude-dir, and list matching files with -l, -L, and --null. This page covers recursive tree search only — not regex engines or pattern files.

Published

Updated

Read time 9 min read

Reviewed byDeepak Prasad

grep Recursive Search in Linux: Directories, Include/Exclude, and File Lists
About Walk a directory tree with grep -r or -R, narrow results with --include and --exclude-dir, and list matching files with -l, -L, and --null. This page covers recursive tree search only — not regex engines or pattern files.
Tested on Ubuntu 25.04 (Plucky Puffin); grep (GNU) 3.11; kernel 7.0.0-27-generic
Package grep
Man page grep(1)
Privilege user (read access to files)
Distros

GNU grep on Linux (Ubuntu, Debian, RHEL, Fedora, SUSE, Arch, and others).

BSD/macOS grep differs slightly — test flags on the target host for portable scripts.

grep — quick reference

Directory recursion

Walk every file under a path. Use -r for normal trees; -R when directory symlinks must be followed.

When to use Command
Search all files under a directory (default: do not follow symlinks into other dirs) grep -r error /var/log
Search the current project tree from . grep -r TODO .
Follow symbolic links to directories as well as files grep -R error /var/log
Show line numbers in recursive output grep -rn error /var/log
Case-insensitive tree search grep -ri error /var/log

Include and exclude globs

Limit which files grep opens inside a recursive walk — essential for codebases and log folders.

When to use Command
Search only certain extensions grep -r error --include='*.log' /var/log
Search several extensions in one run grep -r error --include='*.conf' --include='*.yaml' /etc
Skip noisy file types (compressed logs, binaries) grep -r error --exclude='*.gz' /var/log
Skip VCS or dependency directories grep -r error --exclude-dir='.git' .
Skip several directories with brace expansion grep -r error --exclude-dir={.git,node_modules} .
Skip binary files that would print "Binary file matches" grep -r --binary-files=without-match error /var

Filenames only

When you need which files matched — not every line — use listing modes.

When to use Command
Print only paths that contain a match grep -rl error /var/log
Print only paths with no match grep -rL error /var/log
Null-terminated filenames for xargs -0 pipelines grep -rlZ error /var/log
Count matches per file in a tree grep -rc error /var/log

Tree search output

Control how recursive results are labeled and how errors are handled.

When to use Command
Always prefix each line with the filename (default with multiple files) grep -rH error /var/log
Suppress "Permission denied" and missing-file noise grep -rs error /
Stop after the first match in each file grep -r -m1 error /var/log

grep — command syntax

Synopsis from grep --help on Ubuntu 25.04 (GNU grep 3.11):

text
Usage: grep [OPTION]... PATTERNS [FILE]...
Search for PATTERNS in each FILE.

  -r, --recursive           like --directories=recurse
  -R, --dereference-recursive  likewise, but follow all symlinks
      --include=GLOB        search only files that match GLOB
      --exclude=GLOB        skip files that match GLOB
      --exclude-dir=GLOB    skip directories that match GLOB
  -l, --files-with-matches  print only names of FILEs with matches
  -L, --files-without-match print only names of FILEs with no selected lines
  -Z, --null                print 0 byte after FILE name

With no FILE and -r, grep searches . (the current directory). Combine recursive flags with any pattern option from the grep command or pattern scenarios page.


grep — command examples

Essential Search every file under a directory

The usual starting point: walk a folder and print every matching line with its path.

Set up a small tree and search it:

bash
mkdir -p /tmp/grep-lab/{src,logs}
echo 'error connecting to database' > /tmp/grep-lab/src/app.py
echo 'old error here' > /tmp/grep-lab/logs/archive.log
grep -r error /tmp/grep-lab

Sample output:

text
/tmp/grep-lab/logs/archive.log:old error here
/tmp/grep-lab/src/app.py:error connecting to database

Each line is filename:matching-line. Add -n when you need line numbers inside each file.

Essential Prune .git and node_modules from a codebase search

Dependency and VCS folders dwarf real source files. --exclude-dir skips them without leaving the tree.

Run the command:

bash
mkdir -p /tmp/grep-lab/{src,.git,node_modules}
echo '# TODO: fix timeout' > /tmp/grep-lab/src/app.py
echo 'secret' > /tmp/grep-lab/.git/config
echo 'noise' > /tmp/grep-lab/node_modules/pkg.js
grep -r --exclude-dir=.git --exclude-dir=node_modules TODO /tmp/grep-lab

Sample output:

text
/tmp/grep-lab/src/app.py:# TODO: fix timeout

Repeat --exclude-dir for each folder, or use brace expansion: --exclude-dir={.git,node_modules,cache}.

Common Search only log files with --include

When errors live in *.log but not in configs or scripts, --include limits the walk to matching filenames.

Run the command:

bash
mkdir -p /tmp/grep-lab/{src,logs}
echo 'error connecting to database' > /tmp/grep-lab/src/app.py
echo 'old error here' > /tmp/grep-lab/logs/archive.log
echo 'error in log' > /tmp/grep-lab/logs/app.log
grep -r --include='*.log' error /tmp/grep-lab

Sample output:

text
/tmp/grep-lab/logs/app.log:error in log
/tmp/grep-lab/logs/archive.log:old error here

Stack multiple --include globs to cover more than one extension. Pair with --exclude='*.gz' to skip rotated compressed logs.

Common Skip file types with --exclude

--exclude drops files by name pattern while still recursing into subdirectories.

Run the command:

bash
mkdir -p /tmp/grep-lab/{src,logs}
echo 'error connecting to database' > /tmp/grep-lab/src/app.py
echo 'old error here' > /tmp/grep-lab/logs/archive.log
grep -r --exclude='*.log' error /tmp/grep-lab

Sample output:

text
/tmp/grep-lab/src/app.py:error connecting to database

Use this when log files are too large or you already searched them with --include='*.log'.

Common List filenames only with -rl

Incident triage often starts with "which files mention this string?" — -l prints each path once.

Run the command:

bash
mkdir -p /tmp/grep-lab/{src,logs}
echo 'error connecting to database' > /tmp/grep-lab/src/app.py
echo 'old error here' > /tmp/grep-lab/logs/archive.log
grep -rl error /tmp/grep-lab

Sample output:

text
/tmp/grep-lab/logs/archive.log
/tmp/grep-lab/src/app.py

Pipe into editors or batch tools: grep -rl error . | xargs sed -n '1,5p'. For paths with spaces, add -Z and use xargs -0.

Common Find files that lack a pattern with -rL

-L inverts the listing: paths where the pattern never appears — handy for "which configs never set PasswordAuthentication".

Run the command:

bash
mkdir -p /tmp/grep-lab/{src,logs}
echo 'error connecting to database' > /tmp/grep-lab/src/app.py
echo 'clean line' > /tmp/grep-lab/logs/app.log
echo 'timeout=30' > /tmp/grep-lab/src/config.conf
grep -rL error /tmp/grep-lab

Sample output:

text
/tmp/grep-lab/logs/app.log
/tmp/grep-lab/src/config.conf

These files were searched but contain no line matching error. Paths with a match (like src/app.py) are omitted.

Advanced Safe pipelines with -Z and xargs -0

Filenames can contain spaces or newlines. -Z terminates each path with a NUL byte for xargs -0.

Run the command:

bash
mkdir -p /tmp/grep-lab/logs
echo 'old error here' > /tmp/grep-lab/logs/archive.log
grep -rlZ error /tmp/grep-lab/logs | xargs -0 wc -l

Sample output:

text
1 /tmp/grep-lab/logs/archive.log
  1 total

Without -Z, a path like my logs/error.log would break xargs word splitting.

Advanced Quiet permission errors with -s

Searching / or another user's home prints "Permission denied" on stderr. -s hides those messages so real matches stay visible.

Run the command:

bash
mkdir -p /tmp/grep-lab/{src,logs}
echo 'error connecting to database' > /tmp/grep-lab/src/app.py
echo 'old error here' > /tmp/grep-lab/logs/archive.log
grep -rs error /tmp/grep-lab 2>&1 | head -3
echo "exit: $?"

Sample output:

text
/tmp/grep-lab/logs/archive.log:old error here
/tmp/grep-lab/src/app.py:error connecting to database
exit: 0

-s suppresses grep's own error text; it does not grant read access. Redirect stderr (2>/dev/null) works too but hides all errors, including typos in the path.


grep — when to use / when not

Use recursive grep when Use something else when
  • You need to search every file under a directory — logs, configs, or source trees
  • You want to filter by extension or skip folders with --include / --exclude-dir
  • You need a list of matching filenames (-l, -L) across a tree
  • A simple pattern is enough — see the base grep cheat sheet for everyday flags
  • You need regex engines, alternation, or pattern filesgrep pattern scenarios
  • You must filter by mtime, size, or depth before grepping → find, then grep the results
  • You are searching a single file or piped output → grep command cheat sheet (no -r needed)
  • Very large repos at scale → rg (ripgrep) if installed — similar filters, faster index

grep vs find

grep -r find + grep
Job Walk dirs and match line content Select files first, then grep each
Strength One command, include/exclude globs -maxdepth, -mtime, -size, permissions
Best for "Find this string anywhere under ./src" "Grep only .log files modified today"

Example with find when depth matters:

bash
find /etc -maxdepth 2 -type f -name '*.conf' -exec grep -H timeout {} +

Command One line
grep recursive Directory trees, include/exclude, file lists (this page)

Browse the full index in our Linux commands reference.


grep — interview corner

What is the difference between grep -r and grep -R?

Both walk directories recursively. -r (--recursive) does not follow symbolic links that point to other directories. -R (--dereference-recursive) does — so a symlinked logs/ folder is searched as if it lived inside the tree.

bash
grep -r error ./src    # skips recursing into symlinked dirs
grep -R error ./src    # follows directory symlinks too

A strong answer is:

"grep -r is the default for code and log trees; I use grep -R only when I know directory symlinks should be searched and loops are not a risk."

How do you skip .git and node_modules in a recursive search?

Pass --exclude-dir once per directory name, or use brace expansion on bash:

bash
grep -r pattern . --exclude-dir=.git --exclude-dir=node_modules
grep -r pattern . --exclude-dir={.git,node_modules,vendor}

A strong answer is:

"I add --exclude-dir for each junk folder; brace expansion keeps the one-liner short on bash."

When do you use --include versus --exclude?

--include is a whitelist — only files matching the glob are opened (for example *.log). --exclude is a blacklist — skip matching files but keep walking. You can combine both: include *.log and exclude *.gz.

A strong answer is:

"Include when I know the file type I want; exclude when I need most files except a few noisy types like archives."

When is grep -rl better than plain grep -r?

-l prints each matching path once. Use it when you will open files manually, feed a list to xargs, or check coverage — not when you need every matching line for analysis.

A strong answer is:

"grep -rl answers 'which files' — I pair it with xargs or an editor; grep -r answers 'show me the lines'."

Why use grep -Z with xargs?

Default xargs splits on whitespace. Paths like my logs/app.log break apart. -Z ends each filename with a NUL byte; xargs -0 reads that format safely.

A strong answer is:

"grep -rlZ … | xargs -0 — required when filenames might contain spaces or odd characters."


Troubleshooting

Symptom Likely cause Fix
No matches in a symlinked directory Using -r, not -R Switch to grep -R or grep the target path directly
.git or node_modules still searched Missing --exclude-dir Add --exclude-dir=.git (repeat per folder)
Searches every file type — slow No --include Limit to *.log, *.conf, or your source extensions
Binary file matches noise Binary opened without filter Add --binary-files=without-match
Flood of Permission denied Searching system paths as normal user Use -s, narrow the path, or run with appropriate read access
Duplicate paths in output Symlink loops with -R Avoid -R on unknown trees; search the real directory

Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …