Skip to content
shellmap

Grep recursively with context lines

Search a directory tree for a pattern and print N lines of surrounding context for each match — for code archaeology, log spelunking, or config-file forensics.

How to grep recursively with context lines in each shell

Bashunix
grep -rn -A 3 -B 3 "TODO" .

`-r` recurses. `-n` shows line numbers. `-A 3` prints 3 lines AFTER each match; `-B 3` prints 3 BEFORE; `-C 3` is shorthand for both. Match groups across files separated by `--`. Add `--include="*.py"` to limit to a glob; `--exclude-dir=node_modules` to skip directories. For massive trees, `ripgrep` (`rg`, `brew install ripgrep`) is dramatically faster — `rg -C 3 TODO` does the same and respects `.gitignore` automatically.

Zshunix
grep -rn -A 3 -B 3 "TODO" .

Same external. The `rg` (ripgrep) ecosystem is the modern answer — `rg -C 3 -t py TODO` filters by file type by name (knows `py` = `*.py`, `*.pyi`, etc.). For fancy interactive context-grep in terminal, `fzf` integration: `rg --color=always -C 3 TODO | fzf --ansi`.

Fishunix
grep -rn -A 3 -B 3 "TODO" .

Same external. Fish-style: `set matches (grep -rn -C 3 "TODO" .)`. For pattern lists from a file: `grep -rnf patterns.txt -C 3 .` (one pattern per line in patterns.txt). For "show match plus 10 lines AFTER, then stop" (limit per file): `grep -rn -A 10 -m 1 "TODO" .`.

PowerShellwindows
Get-ChildItem -Recurse -File | Select-String -Pattern "TODO" -Context 3,3

`-Context before,after` is the equivalent of `grep -B -A`. `-Context 3,3` is symmetric (3 before + 3 after). The output is a `[MatchInfo]` object — `.Context.PreContext` and `.Context.PostContext` are string arrays of the surrounding lines. For colourised terminal output, pipe through `Out-String` or use `Select-String` directly (it formats automatically). Limit by extension: `Get-ChildItem -Recurse -File -Filter *.py | Select-String ...`.

cmd.exewindows
findstr /S /N /C:"TODO" *

`/S` recurses; `/N` adds line numbers. cmd `findstr` has **NO context-lines flag** — it prints match-line only. For context: shell out to pwsh: `powershell -NoProfile -Command "Get-ChildItem -Recurse | Select-String TODO -Context 3,3"`. Or install ripgrep: `winget install BurntSushi.ripgrep.MSVC` then `rg -C 3 TODO`.

Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.

Gotchas & notes

  • For source code search across large repos (millions of lines), ripgrep / silver-searcher (`ag`) / git grep (`git grep -C 3 TODO`) are 10-100× faster than grep and respect `.gitignore` automatically — they skip `node_modules`, `target/`, `dist/`, etc., without the `--exclude-dir` boilerplate. Default to `rg` if it's installed; fall back to `grep -r` otherwise.
  • `grep -r` (recursive) follows symlinks by default on GNU; `grep -R` follows them; `grep -r` does NOT follow symlinks on BSD (macOS). Symlink loops can hang the search forever on GNU `-R`; add `--exclude-dir=` to skip known link directories. Use `find . -type f -name "*.py" | xargs grep TODO` for finer control over what gets searched.
  • For binary files mixed into the tree, `grep -r` will scan them and either skip (newer GNU grep treats binary as "no match") or dump unreadable output. Add `--binary-files=without-match` (or `-I` on GNU) to skip binaries entirely. `rg` skips binaries by default.
  • pwsh `Select-String` output object structure is its strength — you can post-filter: `... | Where-Object { $_.Line.Length -lt 200 }` (skip minified lines). You can group by file: `... | Group-Object Path`. Plain-text `grep` output loses this structure; you'd have to awk it back.

Related commands

Related tasks