Linux: Text Search — grep and Regular Expressions
Finding that one line among thousands of files — that's grep's specialty. It's probably the most frequently used command on Linux.
📋 Prerequisites: You should already know
- Lesson 13: Pipes and Filters
1. What You'll Learn
- grep basic search and common options
- grep -r recursive code search
- Basic regular expressions
- Extended regular expressions
- Log analysis in practice
2. A Story of Finding Code Across a Hundred Files
(1) The Pain Point: Changing an API Name, but Not Sure Which Files Use It
Xiaoming needed to change all calls from requests.get to httpx.get in Python files. The project had over 100 files — opening them one by one was impossible.
(2) grep Recursive Search
grep -rn "requests\.get" --include="*.py" src/
Output:
src/api/client.py:15: response = requests.get(url, headers=headers)
src/api/users.py:42: data = requests.get(f"{API_URL}/users")
src/api/orders.py:78: result = requests.get(ORDER_URL)
3 files with calls — found in under a second.
(3) The Benefit: From Needle in a Haystack to Pinpoint Accuracy
Xiaoming formed a habit: before changing code, use grep -rn to find all related locations, confirm them, then start editing.
3. Knowledge Points
(1) grep Basic Usage
# Basic search
grep "pattern" filename
# Common option combinations
grep -rn "TODO" src/ # Recursive search, show line numbers
grep -i "error" log.txt # Ignore case
grep -v "debug" log.txt # Invert match (lines NOT containing debug)
grep -c "failed" log.txt # Count only
grep -l "main" *.py # Show only matching filenames
grep -A5 -B5 "exception" log.txt # Show 5 lines before and after match
| Option | Meaning |
|---|---|
-i |
Ignore case |
-v |
Invert match (show non-matching lines) |
-c |
Output only the count of matching lines |
-n |
Show line numbers |
-r or -R |
Recursive search through subdirectories |
-l |
Show only filenames containing matches |
-L |
Show only filenames NOT containing matches |
-w |
Match whole words |
-A NUM |
Show NUM lines after match |
-B NUM |
Show NUM lines before match |
-C NUM |
Show NUM lines before and after match |
(2) Basic Regular Expressions
| Metachar | Meaning | Example |
|---|---|---|
. |
Match any single character | h.t matches hit / hot / hat |
^ |
Start of line | ^root matches lines starting with root |
$ |
End of line | bash$ matches lines ending with bash |
* |
Previous character repeated 0 or more times | ab*c matches ac / abc / abbc |
[abc] |
Match any character in the set | [Hh]ello matches Hello or hello |
[^abc] |
Match characters NOT in the set | [^0-9] matches non-digit characters |
\< |
Start of word | \<root matches root at the start of a word |
\> |
End of word | root\> matches root at the end of a word |
. matches any single character — if you need to match a literal dot (like in an IP address), you must escape it with a backslash: grep "192\.168\." file instead of grep "192.168." file (the latter would incorrectly match 192X168Y etc.).
(3) Extended Regular Expressions (grep -E)
In basic regex, +, ?, |, {}, () need escaping. Extended regex uses these metacharacters directly (via grep -E or egrep):
ℹ️ Note: The main difference between basic and extended regex is that in extended regex (
grep -E),+,?,|,{},()can be used without backslash escaping. In basic regex,grep "error\|warning"requires escaping|, whilegrep -E "error|warning"does not. Prefergrep -Efor clearer syntax.
| Metachar | Meaning | Example |
|---|---|---|
+ |
Previous character repeated 1 or more times | ab+c matches abc / abbc but not ac |
? |
Previous character appears 0 or 1 time | colou?r matches color or colour |
| ` | ` | Or |
{n,m} |
Repeat n to m times | [0-9]{3,5} matches 3-5 digit numbers |
() |
Grouping | `(error |
(4) ▶ Example: Basic Search
# Search in a file
grep "root" /etc/passwd
# root:x:0:0:root:/root:/bin/bash
# Case-insensitive
echo -e "Error\nerror\nERROR" | grep -i "error"
# All three lines match
# Count
grep -c "nologin" /etc/passwd
# Output: 16
# Show line numbers
grep -n "www-data" /etc/passwd
# 33:www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
Output:
TEXTroot:x:0:0:root:/root:/bin/bash Error error ERROR 16 33:www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
(5) ▶ Example: Invert Match
# Exclude comment lines
grep -v "^#" /etc/ssh/sshd_config | grep -v "^$"
# Exclude debug logs
grep -v "DEBUG" app.log | grep "ERROR"
# Exclude files with certain extensions
ls -la | grep -v "\.md$"
Output:
TEXTPort 22 PermitRootLogin no PasswordAuthentication no 2026-07-07 10:23:45 ERROR Database connection failed 2026-07-07 10:24:12 ERROR Timeout waiting for response drwxr-xr-x 2 alice alice 4096 Jul 7 10:00 src -rw-r--r-- 1 alice alice 1234 Jul 7 10:00 config.yaml
(6) ▶ Example: Recursive Code Search
# Search for all TODO comments in the project
grep -rn "TODO\|FIXME\|HACK" --include="*.py" src/
# Search in specific file types
grep -rn "def " --include="*.py" --include="*.js" .
# Search excluding node_modules
grep -rn "api_key" --exclude-dir=node_modules --exclude-dir=.git .
# Show only filenames
grep -rl "class User" src/
Output:
TEXTsrc/api/client.py:23:# TODO: Add retry logic src/models/user.py:45:# FIXME: Handle edge case src/utils/helpers.py:12:# HACK: Temporary workaround src/api/client.py:10:def get_user(user_id): src/models/user.py:5:def validate_email(email): config/settings.py:3:api_key = "sk-xxx" src/models/user.py src/auth.py
grep -rn is the golden combo for code search — -r recursively searches directories, -n shows line numbers, and --include limits file types. In large projects, grep -rn "pattern" --include="*.py" src/ can locate target code within a second.
(7) ▶ Example: Using Context
# Show 3 lines before and after the match
grep -C 3 "NullPointerException" app.log
# Show 5 lines after the match
grep -A 5 "Traceback" python_error.log
# Show 2 lines before the match
grep -B 2 "500" access.log
Output:
TEXT10:23:01 INFO Processing request 10:23:02 WARN Memory usage high 10:23:03 ERROR NullPointerException at UserService.java:45 10:23:04 INFO Retrying operation 10:23:05 WARN Connection pool low Traceback (most recent call last): File "app.py", line 42, in handle_request result = process(data) File "processor.py", line 15, in process return transform(input) ValueError: Invalid input data 192.168.1.100 - - [07/Jul/2026:10:23:44] "POST /api/data" 192.168.1.100 - - [07/Jul/2026:10:23:45] "POST /api/data HTTP/1.1" 500 0
(8) ▶ Example: Regex in Practice
# Match email addresses
grep -E "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" contacts.txt
# Match IP addresses
grep -E "([0-9]{1,3}\.){3}[0-9]{1,3}" access.log
# Match URLs
grep -E "https?://[^ ]+" index.html
# Match dates (YYYY-MM-DD)
grep -E "[0-9]{4}-[0-9]{2}-[0-9]{2}" logs/*.log
# Match lines starting with a digit and ending with a letter
grep "^[0-9].*[a-zA-Z]$" file.txt
Output:
TEXTalice@example.com bob.smith@company.org contact@my-site.net 192.168.1.100 - - [07/Jul/2026] ... 10.0.0.45 - - [07/Jul/2026] ... https://example.com/api/users http://internal.server.local/status 2026-07-07 2026-07-08 1abc 23x
(9) ▶ Comprehensive Example: Log Error Analysis
#!/bin/bash
# analyze-errors.sh - Log error analysis
LOG_FILE="${1:-/var/log/syslog}"
echo "=== Error Analysis Report ==="
echo "Source file: $LOG_FILE"
echo ""
echo "1. Error count by type:"
grep -i "error" "$LOG_FILE" | awk '{print $5}' | sort | uniq -c | sort -rn | head -10
echo ""
echo "2. Last 5 errors:"
grep -i "error" "$LOG_FILE" | tail -5
echo ""
echo "3. Critical alerts (OOM / Disk Full / Connection Refused):"
grep -iE "oom|out of memory|disk full|no space left|connection refused" "$LOG_FILE"
echo ""
echo "4. Error trend (by hour):"
grep -i "error" "$LOG_FILE" | grep -oP "\d{2}:\d{2}" | cut -d: -f1 | sort | uniq -c | sort -rn
❓ FAQ
Q: What's the difference between grep and regex? A: grep is a command-line tool for searching lines that match a pattern in files; regular expressions (regex) are a syntax for describing text patterns. grep supports regex as search patterns, but regex itself is just a syntax that can also be used in sed, awk, Python, and many other tools and languages.
Q: Are
grep -randgrep -Rthe same? A: Basically yes.-Rfollows symbolic links, while-rdoes not by default. Either works fine for daily use.
Q: How do I search ignoring case? A: Use the
-ioption:grep -ir "error" /var/log/.
Q: Why does grep sometimes fail to find Chinese characters? A: This may be a locale issue. Chinese characters are multi-byte in UTF-8 encoding, and
grepmatches by byte by default. Tryexport LANG=zh_CN.UTF-8orgrep -P(Perl regex mode).
Q: What is
grep -vuseful for? A: Invert match — output lines that do NOT match the pattern. Combined with other grep calls, it can "exclude" certain lines, e.g.,grep -v "^#"excludes comment lines,grep -v "^$"excludes blank lines.
📖 Summary
grep "pattern" filesearches files,-rrecursive directories,-nshows line numbers- Basic regex:
.any character,^start of line,$end of line,*repeat,[abc]character set - Extended regex (
grep -E):+one or more,?zero or one,|or,()grouping -vinvert,-iignore case,-ccount,-lshow only filenames-A/-B/-Cshow context lines
📝 Exercises
- Basic (⭐): Search
/etc/passwdfor users with/bin/bashas their login shell, then search a log file for errors and use-cto count them - Intermediate (⭐⭐): Recursively search a project directory for all FIXME and TODO comments, then use
grep -vto exclude the .git directory and count total lines of code - Challenge (⭐⭐⭐): Use regex to match all IP addresses in a log file, then combine
sortanduniq -cto count occurrences per IP and find the top 10 most frequent IPs