grep in Depth and Regular Expressions

Intermediate
14 min

grep in Depth and Regular Expressions

grep prints lines that match a pattern, and its real power appears when the pattern is a regular expression rather than a fixed word. Regular expressions are a small language for describing text, and the same syntax is used by sed, awk, editors, and most programming languages. After this lesson you will be able to search whole codebases, pull structured data out of logs, and read the regex syntax you will keep meeting for the rest of your career.

grep Options You Will Use Every Day

| Option | Effect | |---|---| | -i | case-insensitive | | -n | show line numbers | | -r / -R | recurse into directories (-R follows symlinks) | | -l / -L | list only filenames that match / do not match | | -c | count matching lines per file | | -v | invert: print lines that do not match | | -w | match whole words only | | -o | print only the matching part, one per line | | -A n / -B n / -C n | lines of context after / before / around | | -E | extended regular expressions | | -F | fixed strings, no regex (fast, safe for special characters) | | --include="*.js" / --exclude-dir=dist | filter files while recursing |

bash
grep -rn --include="*.ts" "TODO" src/ grep -c "GET /api" access.log grep -vE "^\s*(#|$)" /etc/ssh/sshd_config # config without comments and blank lines grep -wl "password" *.env

Regex Building Blocks

A regular expression is matched against each line. Most characters match themselves; a few are metacharacters with special meaning.

| Pattern | Matches | |---|---| | . | any single character | | [abc], [a-z], [^0-9] | one character from a set, a range, or not in a set | | ^, $ | start of line, end of line | | * | zero or more of the preceding item | | \. | a literal dot (escape any metacharacter with \) | | \s, \w, \b | whitespace, word character, word boundary (GNU grep) |

bash
grep "^root:" /etc/passwd # lines starting with root: grep "sh$" /etc/passwd # lines ending in sh grep "colou\?r" text.txt # basic regex: \? makes u optional grep "[0-9][0-9]*" versions.txt # one or more digits

Basic vs Extended Syntax

grep defaults to basic regular expressions (BRE), where +, ?, {}, | and () are literal unless escaped. With -E (or the egrep alias) you get extended regular expressions (ERE), where they are metacharacters without escaping. Use -E by default; it is the syntax that JavaScript, Python and most tools share.

| ERE | Meaning | |---|---| | + | one or more | | ? | zero or one | | {3}, {2,5}, {2,} | exactly 3, 2 to 5, at least 2 | | a\|b | alternation | | (ab)+ | grouping |

bash
grep -E "^(GET|POST) /api/v[0-9]+" access.log grep -E "[0-9]{3}-[0-9]{4}" contacts.txt grep -E "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[a-z]{2,}$" emails.txt grep -oE "https?://[^ \"']+" page.html

Extracting Data with -o

-o prints only the matched text, which turns grep into an extractor. Combined with sort and uniq it becomes a quick analytics tool.

bash
# Top 5 IP addresses hitting the server grep -oE "^[0-9]+(\.[0-9]+){3}" access.log | sort | uniq -c | sort -rn | head -5 # All distinct environment variable names referenced in a script grep -oE '\$[A-Z_]+' deploy.sh | sort -u

Context Lines and Multiple Patterns

bash
grep -n -B 2 -A 5 "Traceback" app.log # 2 lines before, 5 after grep -e "error" -e "warn" app.log # either pattern grep -f patterns.txt app.log # patterns read from a file, one per line grep -E "error|warn" app.log # same with alternation

Fixed Strings and Special Characters

When the search text contains dots, brackets or dollar signs and you want them literal, -F avoids escaping altogether.

bash
grep -F "config[env].url" app.js grep -F -- "--force" install.sh # -- so "--force" is not treated as an option

Performance and Alternatives

grep -r on a large repository is fast, but skip generated directories with --exclude-dir and consider ripgrep (rg), which respects .gitignore and is often several times quicker. Its flags (-i, -n, -o, -C) match grep, so nothing you learned here is wasted.

Common Mistakes

  • Forgetting that . matches any character: grep "1.0" also matches 100. Escape it: "1\.0".
  • Using + or | without -E and wondering why nothing matches.
  • Not quoting the pattern, so the shell expands * or splits on spaces.
  • Anchoring the wrong way: ^ and $ refer to the line, not the file.
Quick Quiz
Question 1 of 3

Which command prints only the filenames that contain the word "TODO"?

Key Takeaways

  • Use -rn for codebase searches, -i for case, -l for filenames, -c for counts and -C for context.
  • Prefer -E so +, ?, {}, | and () work without backslashes.
  • ^, $, ., [] and * are the core metacharacters; escape them with \ to match literally.
  • -o extracts matches, and grep -o | sort | uniq -c | sort -rn is a reusable analytics idiom.
  • -F is the safe, fast choice for literal strings with special characters.

Next lesson: Pipes, Redirection and Command Chaining — connect commands together and control where their input and output go.

grep in Depth and Regular Expressions - Linux & Command Line | CodeYourCraft | CodeYourCraft