grep extracts lines matching a regular expression from text — the foundational search tool for log analysis, code search, and config filtering on any Unix/Linux system. For a faster, modern alternative, ripgrep adds multi-threaded search and automatic .gitignore respect. This article covers grep fundamentals, regex patterns, real-world use cases, and the transition to ripgrep.
"I need to pull only errors from a log file." "I want to find every occurrence of a function across the entire codebase." "I want to see only the active lines in a config file, without comments."
These are the moments when grep steps in. And its evolution, the Rust-powered ripgrep, takes things even further.
What is grep?
grep stands for Global Regular Expression Print. It is a filter command that extracts lines matching a regular expression from text — one of the most fundamental tools in the Unix/Linux world.
Key characteristics:
- Pipeline workhorse — designed to be chained with other commands via
| - Pre-installed on virtually every Linux system — ready to use on any server
- Flexible regex searching — from simple strings to complex patterns
- Lightweight and fast — handles massive log files without breaking a sweat
The name originates from the ed editor command g/re/p (global / regular expression / print), written by Ken Thompson in 1973. It has been in active use for over 50 years. See the GNU grep manual for the full reference.
Basic usage
Let's start with the essential grep options. These alone will cover the vast majority of your day-to-day search tasks.
String search
grep "error" logfile.txt
Every line containing "error" in the file is printed to stdout.
Commonly used options
# Case-insensitive search
grep -i "error" logfile.txt
# Show line numbers
grep -n "error" logfile.txt
# Recursive search through directories
grep -rn "TODO" src/
# Show lines that do NOT match (exclusion filter)
grep -v "debug" logfile.txt
# Count matching lines
grep -c "error" logfile.txt
# Show only filenames that contain a match
grep -l "error" *.log
Context display
When you want to see surrounding lines, use -A (After), -B (Before), or -C (Context).
# Show 3 lines after and 1 line before each match
grep -A 3 -B 1 "error" logfile.txt
# Show 3 lines of context on both sides (same as -A 3 -B 3)
grep -C 3 "error" logfile.txt
Multiple patterns
# OR search (extended regex)
grep -E "error|warning|fatal" logfile.txt
# Fixed-string search (disables regex interpretation)
grep -F "console.log(" src/app.js
Regex patterns
The real power of grep lies in regular expressions. There are two flavors: Basic Regular Expressions (BRE) and Extended Regular Expressions (ERE, enabled with -E). Both are defined in the POSIX regular expression specification.
Common pattern reference
| Pattern | Meaning | Example |
|---|---|---|
^pattern | Start of line | grep "^#" config.txt |
pattern$ | End of line | grep "\.js$" files.txt |
. | Any single character | grep "h.t" words.txt |
[abc] | Character class | grep "[0-9]" data.txt |
[^abc] | Negated character class | grep "[^aeiou]" words.txt |
* | Zero or more of preceding | grep "ab*c" data.txt |
+ (ERE) | One or more of preceding | grep -E "[0-9]+" data.txt |
? (ERE) | Zero or one of preceding | grep -E "colou?r" text.txt |
\b | Word boundary | grep -w "error" log.txt |
{n,m} (ERE) | Between n and m times | grep -E "[0-9]{2,4}" data.txt |
BRE vs ERE
In Basic Regular Expressions (BRE), metacharacters like +, ?, {}, |, and () require a backslash. Extended Regular Expressions (ERE, via -E) let you use them directly.
# BRE: backslash required
grep "error\|warning" logfile.txt
grep "ab\{2,4\}" data.txt
# ERE: cleaner syntax (preferred)
grep -E "error|warning" logfile.txt
grep -E "ab{2,4}" data.txt
PCRE (Perl-Compatible Regular Expressions)
The -P flag enables Perl-compatible regex, unlocking shortcuts like \d (digit), \s (whitespace), and lookahead/lookbehind assertions.
# Match a postal code pattern (3 digits, hyphen, 4 digits)
grep -P "\d{3}-\d{4}" addresses.txt
# Extract email-like strings
grep -oP "[\w.+-]+@[\w-]+\.[\w.]+" contacts.txt
Real-world use cases
Log file analysis
This is the most common use case in server operations.
# Extract errors from a specific date (timestamped logs)
grep "2026-03-08.*ERROR" /var/log/app.log
# Extract IP addresses only
grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" access.log
# Tally HTTP status codes (grep + awk + sort combo)
grep -oE "HTTP/[0-9.]+ [0-9]+" access.log | awk '{print $2}' | sort | uniq -c | sort -rn
Codebase search
For large-scale searches, combine grep with find for more flexible file targeting.
# Find all TODO/FIXME comments across the project
grep -rn "TODO\|FIXME" --include="*.ts" src/
# Locate specific function calls
grep -rn "useState(" --include="*.tsx" src/
# Search import statements
grep -rn "^import.*from" --include="*.ts" --include="*.tsx" src/
Config file filtering
# Strip comments and blank lines to show only active settings
grep -v "^#" /etc/ssh/sshd_config | grep -v "^$"
# Extract a specific section
grep -A 10 "\[database\]" config.ini
Pipeline combinations
grep shines brightest when combined with other commands. Pair it with xargs to pass matched results as arguments to the next command.
# Check running nginx processes (excludes the grep process itself)
ps aux | grep "[n]ginx"
# Search command history for git-related commands
history | grep "git"
# Extract errors from kernel messages
dmesg | grep -i "error"
# Filter a file listing by extension
find . -type f | grep "\.mdx$"
ripgrep: The modern alternative
ripgrep (command: rg) is a grep replacement written in Rust by Andrew Gallant (@BurntSushi). With over 50,000 GitHub stars and adopted as VS Code's internal search engine, it has become the default search tool for many developers.
Key advantages:
- Blazing speed — multi-threaded parallel search
- Automatic .gitignore respect — skips
node_modules,dist, etc. by default - Full Unicode support — handles CJK characters without issues
- Automatic binary file skipping — ignores images and compiled files
- Recursive by default — no need for
-r
Installation
winget install BurntSushi.ripgrep.MSVC
Basic usage
# Recursive search from the current directory (no -r needed)
rg "TODO"
# Search by file type
rg "TODO" --type js
rg "TODO" -t py
# Use glob patterns to narrow the search scope
rg "TODO" -g "*.tsx"
# Fixed-string search (disables regex)
rg -F "console.log("
# Case-insensitive search
rg -i "error"
# Line numbers are always shown by default
rg "error" src/
Advanced features
# JSON output (useful for tool integration)
rg "error" --json
# Replacement preview (does NOT modify files)
rg "old_func" -r "new_func"
# Print only the matched portion (like grep -o)
rg -o "\d{3}-\d{4}" addresses.txt
# Include hidden files in the search
rg --hidden "SECRET_KEY"
# Ignore .gitignore and search all files
rg --no-ignore "password"
.rgignore for custom exclusions
Place an .rgignore file at the project root to define ripgrep-specific exclusions. The syntax is identical to .gitignore.
# .rgignore
dist/
coverage/
*.min.js
*.map
grep vs ripgrep
With both tools understood, here is a side-by-side comparison to guide your decision.
| Feature | grep | ripgrep |
|---|---|---|
| Speed (large repos) | Single-threaded | Multi-threaded, fast |
| .gitignore | Ignores it | Respects automatically |
| Unicode | Limited | Full support |
| Recursive by default | Requires -r | Recursive by default |
| Binary files | Needs explicit options | Auto-skipped |
| Dependencies | None (pre-installed) | Separate install required |
| PCRE support | -P flag | --pcre2 flag |
| Color output | --color=auto | Colored by default |
When to use which:
- On servers — grep is the only option. No extra installation needed.
- On your dev machine — ripgrep is overwhelmingly more comfortable. Install it.
- In shell scripts — use grep for portability. Use rg if the script runs only in your own environment.
Knowing both means you are never stuck. Master grep for the fundamentals, then add ripgrep for productivity.
Advanced techniques
Multiline pattern matching with ripgrep
The --multiline (-U) flag matches patterns that span multiple lines. Useful for finding function definitions or multi-line log entries.
# Find empty function blocks
rg -U "function.*\{[\s]*\}" --type js
# Find try-catch blocks
rg -U "try\s*\{[\s\S]*?\}\s*catch" --type ts
Custom file types in ripgrep
--type-add lets you define custom file types. Handy for project-specific extensions.
# Define and search MDX files
rg --type-add 'mdx:*.mdx' -t mdx "Callout"
# Define a config type with multiple extensions
rg --type-add 'config:*.{conf,cfg,ini,toml,yaml,yml}' -t config "database"
Set export RIPGREP_CONFIG_PATH=~/.rgrc in your dotfiles to persist custom types.
# ~/.rgrc
--type-add=mdx:*.mdx
--type-add=config:*.{conf,cfg,ini,toml,yaml,yml}
--smart-case
Search statistics with --stats
The --stats flag prints match counts, files searched, and elapsed time. Useful for gauging the impact scope before a large refactor.
rg "deprecated_function" --stats
Live grep with ripgrep + fzf
Pipe ripgrep results into fzf for interactive narrowing with previews and direct file opening.
# Interactively filter rg results, open in vim
rg --line-number --no-heading "TODO" | \
fzf --delimiter ':' \
--preview 'bat --color=always --highlight-line {2} {1}' \
--bind 'enter:execute(vim {1} +{2})'
FAQ
Should I use grep or ripgrep?
On your development machine, use ripgrep. It respects .gitignore automatically, runs multi-threaded searches, and has full Unicode support out of the box. On servers or in portable shell scripts, stick with grep — it is pre-installed everywhere. Ideally, learn both.
Why does grep -P not work on macOS?
macOS ships with BSD grep, not GNU grep. The -P flag (PCRE) is a GNU grep feature. Either install GNU grep via brew install grep, or use ripgrep's --pcre2 flag instead.
How do I search all files with ripgrep, ignoring .gitignore?
Use rg --no-ignore "pattern" to skip .gitignore exclusions. To also include hidden files, add --hidden: rg --no-ignore --hidden "pattern".
How do I AND-search with grep (match lines containing two patterns)?
grep has no built-in AND operator. Pipe two greps together: grep "pattern1" file | grep "pattern2". The same approach works with ripgrep.
Can ripgrep actually replace files?
rg "old" -r "new" previews replacements but does not modify files. To apply changes, combine with sed: rg -l "old" | xargs sed -i 's/old/new/g'.
What is the difference between grep -r and grep -R?
-r does not follow symbolic links, while -R does. Use -r by default to avoid accidentally descending into unintended directories through symlinks.
Which editors and tools integrate with ripgrep?
VS Code uses ripgrep as its built-in search engine. Neovim's Telescope, Emacs' counsel-rg, and fzf all integrate with ripgrep. If your IDE's search feels slow, a ripgrep-based plugin is worth trying.
How does ripgrep handle non-UTF-8 files?
ripgrep searches UTF-8 by default and handles BOM-prefixed files. For other encodings, use the -E flag: rg -E shift-jis "pattern". GNU grep can handle non-UTF-8 via the LANG environment variable, but it is more error-prone.
Wrapping Up
grep is the bedrock of text searching, and it is unavoidable if you work with Linux. Combined with regular expressions, it handles log analysis, code search, and config filtering with ease.
ripgrep builds on that foundation, adding significant speed and convenience. Once you experience automatic .gitignore respect and multi-threaded search, there is no going back.
As a next step, explore text transformation with sed and awk, or try combining ripgrep with fzf for interactive search. Pairing search with transformation dramatically boosts your CLI productivity.
Related articles: