The text-search tool everyone reaches for
grep stands for "globally search a regular expression and print." It searches input for lines matching a pattern. The default basic regular expression (BRE) syntax is awkward; almost every modern user adds -E for extended (ERE) or -P for Perl-style (PCRE).
The flags you'll wear out
-i— case-insensitive.-r/-R— recurse into directories.-n— show line numbers.-v— invert: show non-matching lines.-l— only filenames containing matches.-c— count of matches.-A 3— 3 lines of context after each match.-B 3before,-C 3both.-E— extended regex (alternation, +, ?, {n,m} without backslashes).--include='*.py'/--exclude— file filters.--color=auto— highlight matches.
Common patterns
- Recursive case-insensitive:
grep -rin 'TODO' . - Files with matches only:
grep -rl 'pattern' src/ - Skip binaries:
grep -rn --binary-files=without-match 'pattern' . - Count per file:
grep -rc 'TODO' .shows file:count pairs. - Extended regex:
grep -E 'foo|bar' file
The exit code
0 = at least one match, 1 = no match, 2 = error. That makes grep -q 'pattern' file a perfect existence check in scripts: if grep -q ERROR build.log; then ...; fi.
When grep is the wrong tool
For multi-line patterns, structured data (JSON/YAML), or symbol-aware search, reach for the modern alternatives in the next track: ripgrep (rg), jq, ast-grep.