grep searches text for lines matching a pattern. The flag set is small but the gotchas are not: macOS ships BSD grep without -P (PCRE), --include, or --exclude, and --color=auto is a GNU-ism. This page is the reference I keep open while writing shell pipelines, with every command shown in the variant that actually works on the platform you pick at the top.
What is grep used for?
grep is the de facto tool for searching text on the command line. You pass it a pattern (literal string, basic regex, extended regex, or PCRE depending on the flag) and one or more files, and it prints every line that matches. The most common uses: filtering log files for an error string, finding all callers of a function in a codebase, narrowing a long command's output to the parts you care about, and counting matches with -c. Recursive search with -r makes it a quick code-grep tool, though dedicated tools like ripgrep are faster for large repositories. The catch on macOS is that the system grep is BSD, not GNU, so a handful of GNU-only flags (-P, --include, --exclude, the auto value on --color) do not work. The Windows equivalent for most patterns is PowerShell's Select-String cmdlet; the older findstr command works in cmd.exe but lacks proper regex support.
Set your OS, the text to search for, and the path. Every command on this page rewrites itself with your values.
grep by task (jump table)
Skim by intent. Each row links to the section here, or to the dedicated deep-dive article for that task.
| I want to... | Where |
|---|---|
| Search a file for a string | Basic search |
| Search a whole directory tree | Recursive search + deep dive |
| Ignore case | How to grep case-insensitively |
| Match a whole word, not a substring | How to match a whole word |
| Show lines before and after a match | Context lines + deep dive |
| Exclude lines that match (invert) | How to invert a match with -v |
| Search several patterns at once | How to search multiple patterns |
| Count how many matches | How to count matches with -c |
| List only the filenames that match | How to list filenames with -l |
| Exclude files or directories | Filtering by filename + deep dive |
| Understand BRE vs ERE vs PCRE | Regex flavors + deep dive |
| Extract a column from matching lines | grep and print a column |
| Count unique matches / rank them | Count unique matches |
| Choose between grep, ripgrep, and ag | grep vs ripgrep vs ag |
| Handle the macOS BSD vs GNU differences | macOS BSD vs GNU grep |
Basic search
Find a literal string in a file.
grep ':pattern' app.logCase-insensitive search (full reference: how to grep case-insensitively).
grep -i ':pattern' app.logSearch standard input (the most common pipeline use):
journalctl -u nginx | grep '500'Regex flavors: BRE, ERE, PCRE
grep has three regex modes. The same pattern can mean different things in each.
| Mode | Flag | What it gives you | Available on |
|---|---|---|---|
| Basic (BRE) | none (default) | Bare metacharacters: . * ^ $ [...]. +, ?, (), {} need backslashes | GNU, BSD |
| Extended (ERE) | -E | Same as BRE plus +, ?, ` | , (), ` without backslashes |
| Perl-compatible (PCRE) | -P | Full PCRE: lookaheads, lookbehinds, non-greedy *?, \d, \w, named groups | GNU only |
ERE is what most people mean by "regex" today. Reach for -E by default.
grep -E '(error|warn|fatal)' app.logPCRE for things only PCRE can express (lookbehind, \d, non-greedy):
grep -P '(?<=user=)\w+' app.logNote: PowerShell's regex engine is .NET, which supports the same constructs PCRE does. macOS BSD grep is the odd one out.
For a deeper regex tour, see the regex cheat sheet. For specific patterns: match an email address, match a URL, match a hex color code.
Common flags
The flags I reach for daily.
| Flag | Meaning |
|---|---|
-i | Case-insensitive |
-v | Invert match (lines that do NOT match) |
-c | Count matches per file |
-l | List filenames with matches (no line output) |
-L | List filenames WITHOUT matches |
-n | Prefix each line with its line number |
-H | Print filename with each match (default when searching multiple files) |
-h | Suppress filename prefix |
-w | Match whole words only |
-x | Match whole lines only |
-o | Print only the matched part, not the whole line |
-q | Quiet; exit status only (handy in if tests) |
-E | Extended regex (ERE) |
-F | Fixed strings (no regex; safer when matching ., *, etc.) |
-r / -R | Recursive (R follows symlinks) |
-F is underused. If the pattern is a literal string with regex metacharacters in it, -F is faster and avoids escaping.
grep -F '192.168.1.1' access.logRecursive search
Search a whole tree. Full reference: how to grep recursively.
grep -rn ':pattern' :search_path-r is recursive, -n prints line numbers. Add -I (capital i) to skip binary files, which both GNU and BSD support:
grep -rnI ':pattern' :search_pathWorth knowing: GNU -r does NOT follow symlinks. Use -R if you want it to. BSD -r follows symlinks by default. Behavior diverges, so if a script depends on it, be explicit with -R or -r --no-dereference.
Filtering by filename
GNU grep has --include and --exclude. BSD grep does not, so on macOS you pipe find into xargs.
grep -rn --include='*.ts' 'useState' src/Excluding a directory:
grep -rn --exclude-dir=node_modules 'process.env' .If you live on macOS and use these flags daily, brew install grep gives you GNU grep as ggrep. Alias it to grep in your shell rc and the friction goes away. See the find command cheat sheet for the find | xargs pattern in more depth.
Context lines
Show N lines after, before, or around each match. Critical for log triage.
| Flag | Meaning |
|---|---|
-A N | N lines After |
-B N | N lines Before |
-C N | N lines of Context (N before and N after) |
grep -C 3 'Exception' app.log-A, -B, -C all work on BSD and GNU. The long forms (--after-context, --before-context, --context) are GNU only.
Counting, listing, inverting
Count matches per file (full reference: how to count matches with -c, including the line-vs-occurrence trap):
grep -c ':pattern' *.logList only filenames that contain a match (full reference: how to list filenames with -l):
grep -rl ':pattern' :search_pathInvert: show lines that do NOT match. Useful for trimming noise:
grep -v '^#' nginx.conf | grep -v '^$'That last pipeline (drop comments, drop blank lines) is the fastest way to read a heavily-commented config file.
macOS BSD vs GNU grep
The differences that matter, in order of how often they bite.
| Feature | GNU grep | BSD grep (macOS default) |
|---|---|---|
-P (PCRE) | Supported | Not supported |
--include=PATTERN | Supported | Not supported |
--exclude=PATTERN | Supported | Not supported |
--exclude-dir=DIR | Supported | Not supported |
--color=auto | Supported | Use --color only (no value) |
--context long form | Supported | -C N short form only |
Recursive -r symlinks | Does NOT follow | Follows by default |
\d, \w, \b in -E | Treated as literal | Treated as literal |
Quick fix on macOS: install GNU grep via Homebrew and use it as ggrep, or alias it.
brew install grep
alias grep='ggrep'
alias egrep='gegrep'
alias fgrep='gfgrep'Add to ~/.zshrc (or ~/.bash_profile) and reload. Scripts you ship to teammates should not assume GNU grep is present; either invoke ggrep explicitly, or stick to the BSD-compatible subset.
Common pitfalls
1. Unquoted patterns get shell-expanded. With grep *.log file, the *.log is expanded by the shell into a list of files before grep ever runs. Always quote: grep '*.log' file.
2. Patterns starting with - are read as flags. grep -test file fails because grep tries to parse -t as a flag. Use grep -- -test file or grep -e '-test' file.
3. grep -E does not enable PCRE. ERE adds +, ?, |, (), {} but not lookaheads, \d, or non-greedy *?. Those are PCRE only (GNU -P or PowerShell).
4. BSD --color syntax differs. --color=auto works on GNU, fails on BSD grep where the value is rejected. Use bare --color on BSD, or skip the flag and let GREP_OPTIONS (deprecated) or shell aliases handle it.
5. grep -r symlink semantics differ. GNU does not follow symlinks; BSD does. If your search misses (or duplicates) files depending on the host, this is why.
6. grep exits 1 when there is no match. Useful in scripts, surprising in pipelines: set -e will abort the script if no line matches. Either disable set -e for that line or wrap with || true.
7. Multi-line patterns do not work. grep is line-oriented. To match across newlines, use pcregrep -M, ripgrep --multiline, or pipe through tr '\n' ' ' first.
8. PowerShell's Select-String returns objects, not strings. Use .Line or .Matches.Value when consuming output programmatically. findstr in cmd.exe returns text but has weaker regex; prefer Select-String for anything beyond a literal search.
What to do next
Deep dives by task (each one is the dedicated reference for a specific grep use case):
- How to grep recursively through a directory: the
-r/-Rreference - How to exclude matches with grep -v: inverting the match
- How to grep case-insensitively:
-iand the locale caveats - How to count matches with grep -c: the line-vs-occurrence trap
- How to show lines before and after a match:
-A,-B,-C - How to search multiple patterns with grep:
-e,-Ealternation,-f - How to match a whole word with grep -w: word-boundary matching
- grep regex: BRE vs ERE vs PCRE: the three regex modes in depth
- How to list only filenames with grep -l:
-land-L - How to exclude files and directories from grep:
--include,--exclude,--exclude-dir - grep vs ripgrep vs ag: which search tool to use
- How to grep and print a specific column: the grep + awk pipeline
- How to count unique matches: the grep + sort + uniq pipeline
Related cheat sheets:
- find command cheat sheet: the natural pair for
grepwhen you need to filter by filename first. - SSH cheat sheet: for greping logs on remote hosts over SSH.
- curl cheat sheet: when the pipeline starts with an HTTP response you want to grep.
- regex cheat sheet: full regex reference for crafting the patterns you feed into
grep -E/-P. - Bash for loop and Bash while loop: looping over filenames with
grep -loutput. - External: GNU grep manual, FreeBSD grep(1), PowerShell Select-String.
FAQ
Sources
Authoritative references this article was fact-checked against.
- GNU project documentationgnu.org





