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 KarunaratneIshan Karunaratne⏱️ 11 min readUpdated
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

Jump to:

Find a literal string in a file.

bash· Linux (GNU)
grep 'error' app.log

Case-insensitive search.

bash· Linux (GNU)
grep -i 'error' 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.

bash· Linux (GNU)
grep -rn 'TODO' .

-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 'TODO' .

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:

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

List only filenames that contain a match:

bash· Linux (GNU)
grep -rl 'apiKey' .

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. grep *.log file — the *.log is expanded by the shell into a list of files. 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

FAQ

TagsgrepCLIRegexLinuxmacOSBSDPowerShellShell Scripting
Share
Ishan Karunaratne

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years across software, Linux systems, DevOps, and infrastructure — and a more recent focus on AI. Currently Chief Technology Officer at a tech startup in the healthcare space.

Keep reading

Related posts