Master the grep Command
Learn grep from plain string searches to recursive directory scans, regular expressions, context flags, and practical log-analysis one-liners on Linux.
Before you start
- ▸A terminal with bash or zsh
- ▸grep installed (pre-installed on all major Linux distros)
- ▸Basic familiarity with navigating the filesystem using cd and ls
grep is one of the most-used tools on any Linux system. It searches text—files, streams, entire directory trees—for lines matching a pattern. Whether you're hunting a config value, filtering log output, or verifying a script's effect, mastering grep pays immediate dividends. This guide covers practical patterns from basic string matches through extended regular expressions, recursive search, and output control flags.
Basic Syntax
The core form is simple:
grep [OPTIONS] PATTERN [FILE...]
If no file is given, grep reads standard input. The pattern is a Basic Regular Expression (BRE) by default. Most day-to-day work uses either plain strings or Extended Regular Expressions (ERE) via -E.
Plain String Searches
Search a single file
grep "PermitRootLogin" /etc/ssh/sshd_config
Case-insensitive match
grep -i "error" /var/log/syslog
Invert the match (print non-matching lines)
grep -v "^#" /etc/ssh/sshd_config
This strips comment lines—useful for reviewing effective configuration without noise.
Count matching lines
grep -c "FAILED" /var/log/auth.log
Show only the matched portion, not the full line
grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" /var/log/nginx/access.log
-o prints each match on its own line, which makes piping into sort | uniq -c trivial.
Regular Expressions
grep supports three regex flavors: BRE (default), ERE (-E), and Perl-compatible (-P). ERE is the sweet spot for most work—more expressive than BRE without requiring Perl.
Anchors
^— start of line$— end of line
grep "^root" /etc/passwd
grep "bash$" /etc/passwd
Character classes and quantifiers
# ERE: match lines with an HTTP status 4xx or 5xx
grep -E " [45][0-9]{2} " /var/log/nginx/access.log
Alternation with ERE
grep -E "error|warn|crit" /var/log/syslog
In BRE you would need \|; ERE is cleaner and should be your default for anything beyond simple strings.
Word boundaries
# Match the word "log" but not "login" or "syslog"
grep -w "log" /var/log/syslog
-w is equivalent to wrapping the pattern in \b...\b word-boundary anchors.
Perl-compatible regex (PCRE)
# Named groups, lookaheads — available with -P on GNU grep
grep -P "(?<=user=)\w+" /var/log/auth.log
Note: -P is a GNU grep extension. It is not available on the BSD grep shipped by default on macOS; use ggrep (Homebrew) there.
Recursive Search
Search an entire directory tree with -r or -R. The difference: -R follows symbolic links; -r does not. Prefer -r unless you specifically need symlink traversal to avoid surprises.
grep -r "ServerName" /etc/apache2/
Show filenames only
grep -rl "TODO" ~/projects/
-l lists each matching file once instead of printing every matching line—much less noise when scanning large trees.
Limit by file type with --include
grep -r --include="*.conf" "Listen" /etc/
grep -r --include="*.py" "import os" ~/projects/
Exclude directories
grep -r --exclude-dir=".git" --exclude-dir="node_modules" "apiKey" ~/projects/
Context Lines
Matching lines are often meaningless without surrounding context. Three flags control this:
-A N— print N lines after the match-B N— print N lines before the match-C N— print N lines before and after (equivalent to-A N -B N)
# Show 3 lines of context around every "OOM" kernel message
grep -C 3 "Out of memory" /var/log/kern.log
# See what follows a failed SSH authentication attempt
grep -A 2 "Failed password" /var/log/auth.log
When context is enabled, grep separates match groups with a -- separator line. Pass --no-group-separator if you're piping the output to another tool and that separator breaks parsing.
Useful Flags Reference
| Flag | Effect |
|---|---|
-i | Case-insensitive matching |
-v | Invert match |
-c | Print count of matching lines per file |
-l | Print filenames with matches only |
-L | Print filenames with no matches |
-n | Prefix output with line numbers |
-o | Print only the matched text, one match per line |
-w | Match whole words only |
-x | Match whole lines only |
-E | Use extended regex (ERE) |
-F | Treat pattern as fixed string (no regex), fastest option |
-P | Perl-compatible regex (GNU grep only) |
-m N | Stop after N matches |
--color=auto | Highlight matches (usually set in ~/.bashrc alias) |
Practical Combinations
Filter systemd journal output
journalctl -u nginx --since today | grep -E "error|emerg|crit"
Extract unique IPs from an access log
grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}" /var/log/nginx/access.log | sort -u
Find files that do NOT contain a pattern (useful for auditing)
grep -rL "ENCRYPTION_KEY" /etc/myapp/
Search compressed logs without decompressing
zgrep "segfault" /var/log/kern.log.2.gz
zgrep is a wrapper around grep that handles gzip-compressed files transparently. It ships alongside grep on all major distros.
Verification
Confirm your grep version and which features are compiled in:
grep --version
Output will look something like grep (GNU grep) 3.11 and list supported options. If -P is missing, the binary was compiled without PCRE support—install grep from your package manager or use -E instead.
Troubleshooting
- No output, exit code 1: No lines matched. This is normal and not an error. Exit code 0 means matches found; 1 means none; 2 means a real error (bad option, unreadable file).
- "Binary file matches":
grepdetected a non-text file. Add-ato force text treatment, or--includeto restrict the search to the right file types. - Pattern containing special shell characters: Single-quote your pattern (
'pattern') to prevent the shell from interpreting$,*,!, etc. beforegrepsees them. - Slow recursive searches: Use
-Ffor fixed strings (no regex overhead), add--exclude-dirto skip large irrelevant trees, or consider ripgrep (rg) for much faster recursive search on large codebases. - ERE quantifier not working: You are probably using BRE. Switch to
grep -Eor useegrep(deprecated alias forgrep -E).
Frequently asked questions
- What is the difference between grep -E and grep -P?
- -E uses Extended Regular Expressions (ERE), which are portable across all POSIX systems. -P uses Perl-compatible regular expressions (PCRE), which add lookaheads, lookbehinds, and named groups, but is a GNU grep extension and not available everywhere.
- Why does my grep pattern with special characters return unexpected results?
- The shell interprets characters like $, *, and ! before grep sees them. Always wrap your pattern in single quotes to pass it to grep literally.
- Is there a faster alternative to grep for large codebases?
- Yes. ripgrep (rg) is significantly faster because it uses Rust's regex engine, respects .gitignore by default, and searches in parallel. Install it with apt install ripgrep, dnf install ripgrep, or pacman -S ripgrep.
- How do I search inside gzip-compressed log files?
- Use zgrep instead of grep. It decompresses on the fly and accepts the same flags. For other formats, bzgrep handles bzip2 and xzgrep handles xz-compressed files.
- Can I use grep to search only specific file types in a recursive scan?
- Yes. Use --include='*.py' to restrict matches to files matching a glob pattern. You can specify --include multiple times and combine it with --exclude-dir to skip directories like node_modules or .git.
Related guides
Bash Arrays and Associative Arrays
Master bash indexed and associative arrays: declaration, element access, looping, mapfile, namerefs, and practical patterns for real scripting work.
Bash Functions and Variable Scoping
Master Bash function scoping with local variables, source-based libraries, correct use of return codes, and array passing techniques including namerefs.
Bash Loops: for, while and until
Learn all three Bash loop types — for, while, and until — with practical, copy-paste examples covering file iteration, counting, polling, and safe line reading.
Bash Scripting for Beginners
Learn Bash scripting from scratch: shebang lines, variables, conditionals, loops, and arguments, plus a real backup script to tie it all together.