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):
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 nameWith 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:
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-labSample output:
/tmp/grep-lab/logs/archive.log:old error here
/tmp/grep-lab/src/app.py:error connecting to databaseEach 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:
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-labSample output:
/tmp/grep-lab/src/app.py:# TODO: fix timeoutRepeat --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:
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-labSample output:
/tmp/grep-lab/logs/app.log:error in log
/tmp/grep-lab/logs/archive.log:old error hereStack 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:
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-labSample output:
/tmp/grep-lab/src/app.py:error connecting to databaseUse 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:
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-labSample output:
/tmp/grep-lab/logs/archive.log
/tmp/grep-lab/src/app.pyPipe 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:
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-labSample output:
/tmp/grep-lab/logs/app.log
/tmp/grep-lab/src/config.confThese files were searched but contain no line matching error. Paths with a match (like src/app.py) are omitted.
Advanced Follow directory symlinks with -R
-r reads symlinked files but does not recurse into symlinked directories. -R follows both.
Run the commands:
Point a short command name at the install path with ln -sf; the create symbolic link on Linux covers -s, -f, and safe targets under /usr/local/bin.
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
ln -sf ../logs /tmp/grep-lab/src/logs_link
grep -r error /tmp/grep-lab/src/
grep -R error /tmp/grep-lab/src/Sample output (grep -r):
/tmp/grep-lab/src/app.py:error connecting to databaseSample output (grep -R — also searches through logs_link):
/tmp/grep-lab/src/logs_link/archive.log:old error here
/tmp/grep-lab/src/app.py:error connecting to databaseUse -R carefully on / or home directories — symlink loops and duplicate paths are possible.
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:
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 -lSample output:
1 /tmp/grep-lab/logs/archive.log
1 totalWithout -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:
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:
/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 |
|---|---|
|
|
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:
find /etc -maxdepth 2 -type f -name '*.conf' -exec grep -H timeout {} +Related commands
| 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.
grep -r error ./src # skips recursing into symlinked dirs
grep -R error ./src # follows directory symlinks tooA 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:
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 |

