TechEarl

grep Cheat Sheet: Examples, Regex Flags, and macOS BSD Differences

A scannable grep reference with the flags I actually use, the GNU vs BSD differences that bite on macOS, and the Windows equivalents (Select-String, findstr) for the same patterns.

Ishan Karunaratne⏱️ 13 min readUpdated
Share thisCopied
grep cheat sheet: -E -P -i -v -r --include --exclude -A -B -C with macOS BSD vs GNU grep differences, Select-String equivalents on Windows, and pipeline patterns with find and xargs.

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.

Try it with your own values

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 stringBasic search
Search a whole directory treeRecursive search + deep dive
Ignore caseHow to grep case-insensitively
Match a whole word, not a substringHow to match a whole word
Show lines before and after a matchContext lines + deep dive
Exclude lines that match (invert)How to invert a match with -v
Search several patterns at onceHow to search multiple patterns
Count how many matchesHow to count matches with -c
List only the filenames that matchHow to list filenames with -l
Exclude files or directoriesFiltering by filename + deep dive
Understand BRE vs ERE vs PCRERegex flavors + deep dive
Extract a column from matching linesgrep and print a column
Count unique matches / rank themCount unique matches
Choose between grep, ripgrep, and aggrep vs ripgrep vs ag
Handle the macOS BSD vs GNU differencesmacOS BSD vs GNU grep

Find a literal string in a file.

bash· Linux (GNU)
grep ':pattern' app.log

Case-insensitive search (full reference: how to grep case-insensitively).

bash· Linux (GNU)
grep -i ':pattern' app.log

Search standard input (the most common pipeline use):

bash· Linux (GNU)
journalctl -u nginx | grep '500'

Regex flavors: BRE, ERE, PCRE

grep has three regex modes. The same pattern can mean different things in each.

ModeFlagWhat it gives youAvailable on
Basic (BRE)none (default)Bare metacharacters: . * ^ $ [...]. +, ?, (), {} need backslashesGNU, BSD
Extended (ERE)-ESame as BRE plus +, ?, `, (), ` without backslashes
Perl-compatible (PCRE)-PFull PCRE: lookaheads, lookbehinds, non-greedy *?, \d, \w, named groupsGNU only

ERE is what most people mean by "regex" today. Reach for -E by default.

bash· Linux (GNU)
grep -E '(error|warn|fatal)' app.log

PCRE for things only PCRE can express (lookbehind, \d, non-greedy):

bash· Linux (GNU)
grep -P '(?<=user=)\w+' app.log

Note: 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.

FlagMeaning
-iCase-insensitive
-vInvert match (lines that do NOT match)
-cCount matches per file
-lList filenames with matches (no line output)
-LList filenames WITHOUT matches
-nPrefix each line with its line number
-HPrint filename with each match (default when searching multiple files)
-hSuppress filename prefix
-wMatch whole words only
-xMatch whole lines only
-oPrint only the matched part, not the whole line
-qQuiet; exit status only (handy in if tests)
-EExtended regex (ERE)
-FFixed strings (no regex; safer when matching ., *, etc.)
-r / -RRecursive (R follows symlinks)

-F is underused. If the pattern is a literal string with regex metacharacters in it, -F is faster and avoids escaping.

bash· Linux (GNU)
grep -F '192.168.1.1' access.log

Search a whole tree. Full reference: how to grep recursively.

bash· Linux (GNU)
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:

bash· Linux (GNU)
grep -rnI ':pattern' :search_path

Worth 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.

bash· Linux (GNU)
grep -rn --include='*.ts' 'useState' src/

Excluding a directory:

bash· Linux (GNU)
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.

FlagMeaning
-A NN lines After
-B NN lines Before
-C NN lines of Context (N before and N after)
bash· Linux (GNU)
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):

bash· Linux (GNU)
grep -c ':pattern' *.log

List only filenames that contain a match (full reference: how to list filenames with -l):

bash· Linux (GNU)
grep -rl ':pattern' :search_path

Invert: show lines that do NOT match. Useful for trimming noise:

bash· Linux (GNU)
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.

FeatureGNU grepBSD grep (macOS default)
-P (PCRE)SupportedNot supported
--include=PATTERNSupportedNot supported
--exclude=PATTERNSupportedNot supported
--exclude-dir=DIRSupportedNot supported
--color=autoSupportedUse --color only (no value)
--context long formSupported-C N short form only
Recursive -r symlinksDoes NOT followFollows by default
\d, \w, \b in -ETreated as literalTreated as literal

Quick fix on macOS: install GNU grep via Homebrew and use it as ggrep, or alias it.

bash
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):

Related cheat sheets:

FAQ

Sources

Authoritative references this article was fact-checked against.

TagsgrepCLIRegexLinuxmacOSBSDPowerShellShell Scripting

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Software Systems Architect · Senior Software Engineer · Engineering Leadership

Software systems architect and senior software engineer with more than two decades designing, building, and running production software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Now a CTO, though what I write here is drawn from the full arc of that work, across architecture, engineering, and operations, not any single job.

Keep reading

Related posts

Practical LFImap reference by task: targeting, traversal, PHP wrappers, command injection, RFI, cookies, proxying, output. Real upstream flags.

LFImap Cheat Sheet: Every Flag I Actually Use

A field-tested LFImap reference: target selection, traversal wordlists, PHP wrappers (filter/input/data/expect/file), command injection, RFI, log/proxy/cookie shaping, second-order requests, and the `PWN` placeholder. Grounded in the real argparse surface.