A bare grep prints only the matching line. Most of the time that is not enough: an error line means little without the stack frame above it, and a config key means little without the block it sits in. grep solves this with three context flags. grep -C 3 'pattern' file prints each matching line plus 3 lines on either side, which is the one command I reach for during log triage.
The three flags are small but each does one thing: -A N shows N lines After the match, -B N shows N lines Before, and -C N shows N lines of Context on both sides. The count argument is mandatory. Forget it and grep reads the next token as the count, which is the single most common mistake with these flags.
Set your values
Set your OS, the file to search, and the pattern. Every grep example below updates with your values.
The one-liner
grep -C 3 ':pattern' :search_pathThat prints every line containing the pattern, with the 3 lines before and the 3 lines after each one. On PowerShell, Select-String -Context takes a before,after pair, so -Context 3,3 is the equivalent of grep -C 3.
The three context flags
-A, -B, and -C are the whole feature. Each takes a numeric count.
| Flag | Long form (GNU) | Meaning |
|---|---|---|
-A N | --after-context=N | The matching line plus N lines after it |
-B N | --before-context=N | N lines before plus the matching line |
-C N | --context=N | N lines before, the match, N lines after |
-C N is exactly -A N -B N with the same count on both sides. The short forms work on both GNU and BSD grep. The long forms are GNU only, so a script that needs to run on macOS should stick to -A, -B, -C.
After context, the matching line itself is included in every case. -A 3 gives you 4 lines total per match (the line plus 3), not 3.
Lines after the match
-A is the one I use most for logs, because the interesting detail in a stack trace is usually below the line that names the exception.
grep -A 10 ':pattern' :search_pathTen lines after the match is usually enough to capture a Java or Python stack trace in one block.
Lines before the match
-B is the inverse. Reach for it when the match is a symptom and the cause is above it: a "connection refused" line whose preceding lines show the failed handshake.
grep -B 5 ':pattern' :search_pathAsymmetric context
-C is symmetric, but you rarely want the same count on both sides. Pass -A and -B together with different counts when the interesting context is lopsided. A config block, for example, usually starts a couple of lines above the key and runs well below it:
grep -A 5 -B 1 ':pattern' :search_pathThat prints 1 line before and 5 lines after. When you mix -A, -B, and -C in one invocation, grep does not combine them: -C overrides whatever -A and -B you also passed. So grep -A 5 -B 1 -C 3 behaves as -C 3 and silently discards the -A 5 -B 1. Drop -C entirely when you want asymmetry.
Context plus line numbers
Add -n so each printed line carries its line number. This turns a context block into something you can jump to directly in an editor, and it makes clear where the matching line sits inside the block.
grep -n -C 3 ':pattern' :search_pathWith -n, GNU grep prints the matching line with a colon after the number and the context lines with a hyphen after the number. So 42: is a match and 41- is a context line. That punctuation difference is the fastest way to scan a long output and find the actual hits.
The group separator between blocks
When two matches are far apart, grep prints each context block separately and divides them with a line containing two hyphens. That separator line is literally the string written as two ASCII hyphen characters, printed on its own line between non-adjacent groups. If two matches are close enough that their context windows overlap or touch, grep merges them into one block and prints no separator.
GNU grep lets you customize that divider. --group-separator=STRING swaps the two-hyphen line for any string you choose, and --no-group-separator removes it entirely.
grep --no-group-separator -C 2 ':pattern' :search_path--group-separator and --no-group-separator are GNU-only. BSD grep on macOS always prints the two-hyphen line and gives you no way to suppress it, which matters when a script downstream parses grep output (see the mistakes section below).
Context plus recursive search
Context flags compose with -r, so you can pull a context window out of every file in a tree at once. Add -n to keep the output navigable, since recursive output also carries the filename.
grep -rn -C 3 ':pattern' .With recursive context, GNU grep prints the group separator both between matches in the same file and between files. Each match line is prefixed file:line: and each context line file-line-, so the colon-versus-hyphen rule still tells you which lines actually matched.
Why context matters for log forensics
The reason these flags exist is that a log line is rarely self-contained. Two cases come up constantly.
A stack trace is a block: the line naming the exception, then a dozen frames below it. Grepping for Exception with no context gives you the headline and throws away the diagnosis. grep -A 20 'Exception' app.log keeps the whole trace.
A config block is also a block: the line you searched for sits inside a stanza that only makes sense with its neighbours. Grepping an nginx config for proxy_pass with -A 3 -B 3 shows you the surrounding location block, not just the one directive.
In both cases the match is a pointer and the context is the payload. That is why -C is the default move for any "what happened around this error" question.
macOS BSD grep vs GNU grep
macOS ships BSD grep. The context flags mostly behave the same, but the separator handling diverges.
| Feature | GNU grep | BSD grep (macOS default) |
|---|---|---|
-A N, -B N, -C N | Supported | Supported |
--after-context, --before-context, --context long forms | Supported | Not supported |
| Two-hyphen group separator between blocks | Printed | Printed |
--group-separator=STRING | Supported | Not supported |
--no-group-separator | Supported | Not supported |
| Merging overlapping context windows | Merged, no separator | Merged, no separator |
If you need to customize or suppress the separator on macOS, brew install grep gives you GNU grep as ggrep. For the short flags -A, -B, -C alone, the system BSD grep is fine and portable.
Common mistakes
1. Forgetting the count argument. grep -A 'pattern' file makes grep read pattern as the count for -A, fail to parse it as a number, and error out or treat your real file as the pattern. The count is mandatory and must come right after the flag: grep -A 3 'pattern' file.
2. Expecting -C to add to -A and -B. When -C appears alongside -A or -B, -C wins and the others are discarded. grep -A 10 -C 2 is just -C 2. For asymmetric context, use -A and -B only and leave -C out.
3. Scripts choking on the separator. The two-hyphen line that grep prints between blocks is not a match and not a context line. A script that does grep -A 2 'x' file | wc -l or pipes context output into a parser will count or mis-handle those separator lines. Strip them with --no-group-separator on GNU grep, or filter the line out with a second grep on BSD.
4. Assuming context lines are matches. Every line in a context block prints, including the surrounding non-matching lines. If you then pipe that into another grep or a counter, you are working with the whole block, not just the hits. Use -n and key off the colon-versus-hyphen punctuation, or count with grep -c separately.
5. Huge counts on huge files. grep -C 5000 'pattern' big.log will happily print thousands of lines per match. If the count is large enough that you want the whole region, you probably want a pager instead.
When NOT to use context flags
Context flags are for pulling a bounded window around scattered matches. Two cases call for a different tool.
When you actually want the whole file or a large contiguous region, do not crank -C up to a huge number. Open the file in a pager (less app.log, then /pattern to search and n to step through matches) so you can scroll freely in both directions without grep deciding the boundaries for you.
When you want structured extraction, not a visual window, reach for awk. If the task is "print every line from the match until a blank line" or "pull field 3 of the line two rows below each match", that is range-pattern and record logic, which awk expresses directly. grep context is a fixed line count; awk handles data-dependent boundaries.
See also
- grep cheat sheet: the full grep reference covering regex modes, recursive search, filename filters, and the BSD vs GNU divergences
- find command cheat sheet: pair
find ... -print0 | xargs -0 grep -C 3to add context search across a filtered file set - find files modified in the last 7 days: narrow the file set by recency before greping context out of it
- External: GNU grep manual, FreeBSD grep(1) man page.





