100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Bash

grep and Regular Expressions

Master text searching with grep and the regular expression syntax that powers it, from basic literal matches to extended regex patterns and useful flags.

Text Processing ToolsBeginner10 min readJul 9, 2026
Analogies

grep and Regular Expressions

grep (Global Regular Expression Print) searches input for lines matching a pattern and prints them — it's one of the most frequently used tools in any shell workflow, from filtering log files to searching source code. Its real power comes from regular expressions (regex): a compact language for describing text patterns far more flexible than literal substring matching. Understanding grep's regex dialects and its most useful flags turns it from a simple search tool into a precise text-filtering instrument.

🏏

Cricket analogy: grep is like a scout scanning years of match footage for every instance a batter played the reverse sweep, and regex is what lets that search go beyond just 'reverse sweep' to flexible patterns like any sweep shot variant at all.

Basic vs extended regular expressions

GNU grep supports two regex dialects: Basic Regular Expressions (BRE), the default, and Extended Regular Expressions (ERE), enabled with -E (or via the egrep alias, which is deprecated in favor of grep -E). In BRE, metacharacters like +, ?, |, (, and ) are treated as literal characters unless escaped with a backslash (\+, \?, \|); in ERE they are metacharacters by default and must be escaped to be literal. GNU grep also supports -P for Perl-compatible regular expressions (PCRE), which adds features like non-greedy quantifiers (*?), lookahead ((?=...)), and \d/\w/\s shorthand classes not available in POSIX regex at all.

🏏

Cricket analogy: BRE versus ERE is like the difference between old-school scoring notation where symbols mean nothing special unless marked, versus modern scoring apps where symbols like '4' or '6' are always meaningful markers unless you escape them as literal digits written out.

bash
# Basic literal search, case-insensitive, recursive
grep -ri 'connection refused' /var/log/

# Extended regex: alternation and quantifiers without backslashes
grep -E '(error|warn|fatal)' app.log
grep -E 'user[0-9]{3,5}@' users.csv

# Show line numbers and 2 lines of context around each match
grep -n -C 2 'NullPointerException' server.log

# Invert match: lines NOT containing a pattern
grep -v '^#' /etc/ssh/sshd_config

# Count matches, and list only filenames that match
grep -c 'TODO' *.py
grep -l 'import requests' *.py

# Whole-word match and PCRE lookahead (GNU grep -P)
grep -w 'cat' animals.txt
grep -P '\d{3}-\d{2}-\d{4}' records.txt   # SSN-like pattern

Anchors, character classes, and quantifiers

^ anchors a match to the start of a line and $ to the end, so ^ERROR only matches lines beginning with ERROR. Character classes like [abc] match any one of the listed characters, [^abc] matches any character except those listed, and POSIX classes like [[:digit:]], [[:alpha:]], and [[:space:]] are portable across locales in a way [0-9] isn't guaranteed to be everywhere (though in practice [0-9] works fine on virtually all modern Linux systems). Quantifiers control repetition: * means zero or more, + means one or more (ERE/PCRE, or \+ in BRE), ? means zero or one, and {n,m} means between n and m repetitions. Combining these lets you build precise patterns, e.g. ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ as a rough (not fully correct) IPv4 matcher.

🏏

Cricket analogy: Using ^ERROR to anchor a match to line-start is like a scorer only counting a boundary if the ball crosses the rope on the very first bounce, and quantifiers like {1,3} are like specifying a batter must have scored between 1 and 3 runs off that specific delivery.

grep's name comes from the ed editor command g/re/p — 'globally search for a regular expression and print matching lines' — a piece of Unix history baked directly into the tool's name, dating back to the earliest versions of Unix in the early 1970s.

Unquoted glob-like patterns passed to grep can be expanded by the shell before grep ever sees them. For example, grep [test].log *.log risks the shell interpreting [test] as a filename glob if matching files exist in the current directory. Always quote your pattern: grep '[test]' *.log.

  • grep searches text line by line for a pattern and prints matching lines; -E enables extended regex, -P enables Perl-compatible regex.
  • In BRE (default), +, ?, |, (, ) are literal unless escaped; in ERE they're metacharacters unless escaped.
  • ^ and $ anchor to line start/end; character classes ([abc], [^abc], [[:digit:]]) match sets of characters.
  • Useful flags: -i (case-insensitive), -v (invert match), -n (line numbers), -c (count), -l (filenames only), -r (recursive), -w (whole word), -C (context lines).
  • Always quote regex patterns to prevent the shell from glob-expanding special characters before grep runs.
  • PCRE mode (-P) adds features POSIX regex lacks, like \d, \w, and non-greedy quantifiers.

Practice what you learned

Was this page helpful?

Topics covered

#Bash#LinuxShellScriptingStudyNotes#DevOps#GrepAndRegularExpressions#Grep#Regular#Expressions#Extended#StudyNotes#SkillVeris