$linuxjunkies
>

Master the grep Command

Learn grep from plain string searches to recursive directory scans, regular expressions, context flags, and practical log-analysis one-liners on Linux.

BeginnerUbuntuDebianFedoraArch9 min readUpdated June 7, 2026

Before you start

  • A terminal with bash or zsh
  • grep installed (pre-installed on all major Linux distros)
  • Basic familiarity with navigating the filesystem using cd and ls

grep is one of the most-used tools on any Linux system. It searches text—files, streams, entire directory trees—for lines matching a pattern. Whether you're hunting a config value, filtering log output, or verifying a script's effect, mastering grep pays immediate dividends. This guide covers practical patterns from basic string matches through extended regular expressions, recursive search, and output control flags.

Basic Syntax

The core form is simple:

grep [OPTIONS] PATTERN [FILE...]

If no file is given, grep reads standard input. The pattern is a Basic Regular Expression (BRE) by default. Most day-to-day work uses either plain strings or Extended Regular Expressions (ERE) via -E.

Plain String Searches

Search a single file

grep "PermitRootLogin" /etc/ssh/sshd_config

Case-insensitive match

grep -i "error" /var/log/syslog

Invert the match (print non-matching lines)

grep -v "^#" /etc/ssh/sshd_config

This strips comment lines—useful for reviewing effective configuration without noise.

Count matching lines

grep -c "FAILED" /var/log/auth.log

Show only the matched portion, not the full line

grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" /var/log/nginx/access.log

-o prints each match on its own line, which makes piping into sort | uniq -c trivial.

Regular Expressions

grep supports three regex flavors: BRE (default), ERE (-E), and Perl-compatible (-P). ERE is the sweet spot for most work—more expressive than BRE without requiring Perl.

Anchors

  • ^ — start of line
  • $ — end of line
grep "^root" /etc/passwd
grep "bash$" /etc/passwd

Character classes and quantifiers

# ERE: match lines with an HTTP status 4xx or 5xx
grep -E " [45][0-9]{2} " /var/log/nginx/access.log

Alternation with ERE

grep -E "error|warn|crit" /var/log/syslog

In BRE you would need \|; ERE is cleaner and should be your default for anything beyond simple strings.

Word boundaries

# Match the word "log" but not "login" or "syslog"
grep -w "log" /var/log/syslog

-w is equivalent to wrapping the pattern in \b...\b word-boundary anchors.

Perl-compatible regex (PCRE)

# Named groups, lookaheads — available with -P on GNU grep
grep -P "(?<=user=)\w+" /var/log/auth.log

Note: -P is a GNU grep extension. It is not available on the BSD grep shipped by default on macOS; use ggrep (Homebrew) there.

Search an entire directory tree with -r or -R. The difference: -R follows symbolic links; -r does not. Prefer -r unless you specifically need symlink traversal to avoid surprises.

grep -r "ServerName" /etc/apache2/

Show filenames only

grep -rl "TODO" ~/projects/

-l lists each matching file once instead of printing every matching line—much less noise when scanning large trees.

Limit by file type with --include

grep -r --include="*.conf" "Listen" /etc/
grep -r --include="*.py" "import os" ~/projects/

Exclude directories

grep -r --exclude-dir=".git" --exclude-dir="node_modules" "apiKey" ~/projects/

Context Lines

Matching lines are often meaningless without surrounding context. Three flags control this:

  • -A N — print N lines after the match
  • -B N — print N lines before the match
  • -C N — print N lines before and after (equivalent to -A N -B N)
# Show 3 lines of context around every "OOM" kernel message
grep -C 3 "Out of memory" /var/log/kern.log
# See what follows a failed SSH authentication attempt
grep -A 2 "Failed password" /var/log/auth.log

When context is enabled, grep separates match groups with a -- separator line. Pass --no-group-separator if you're piping the output to another tool and that separator breaks parsing.

Useful Flags Reference

FlagEffect
-iCase-insensitive matching
-vInvert match
-cPrint count of matching lines per file
-lPrint filenames with matches only
-LPrint filenames with no matches
-nPrefix output with line numbers
-oPrint only the matched text, one match per line
-wMatch whole words only
-xMatch whole lines only
-EUse extended regex (ERE)
-FTreat pattern as fixed string (no regex), fastest option
-PPerl-compatible regex (GNU grep only)
-m NStop after N matches
--color=autoHighlight matches (usually set in ~/.bashrc alias)

Practical Combinations

Filter systemd journal output

journalctl -u nginx --since today | grep -E "error|emerg|crit"

Extract unique IPs from an access log

grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}" /var/log/nginx/access.log | sort -u

Find files that do NOT contain a pattern (useful for auditing)

grep -rL "ENCRYPTION_KEY" /etc/myapp/

Search compressed logs without decompressing

zgrep "segfault" /var/log/kern.log.2.gz

zgrep is a wrapper around grep that handles gzip-compressed files transparently. It ships alongside grep on all major distros.

Verification

Confirm your grep version and which features are compiled in:

grep --version

Output will look something like grep (GNU grep) 3.11 and list supported options. If -P is missing, the binary was compiled without PCRE support—install grep from your package manager or use -E instead.

Troubleshooting

  • No output, exit code 1: No lines matched. This is normal and not an error. Exit code 0 means matches found; 1 means none; 2 means a real error (bad option, unreadable file).
  • "Binary file matches": grep detected a non-text file. Add -a to force text treatment, or --include to restrict the search to the right file types.
  • Pattern containing special shell characters: Single-quote your pattern ('pattern') to prevent the shell from interpreting $, *, !, etc. before grep sees them.
  • Slow recursive searches: Use -F for fixed strings (no regex overhead), add --exclude-dir to skip large irrelevant trees, or consider ripgrep (rg) for much faster recursive search on large codebases.
  • ERE quantifier not working: You are probably using BRE. Switch to grep -E or use egrep (deprecated alias for grep -E).
tested on:Ubuntu 24.04Fedora 40Arch rollingDebian 12

Frequently asked questions

What is the difference between grep -E and grep -P?
-E uses Extended Regular Expressions (ERE), which are portable across all POSIX systems. -P uses Perl-compatible regular expressions (PCRE), which add lookaheads, lookbehinds, and named groups, but is a GNU grep extension and not available everywhere.
Why does my grep pattern with special characters return unexpected results?
The shell interprets characters like $, *, and ! before grep sees them. Always wrap your pattern in single quotes to pass it to grep literally.
Is there a faster alternative to grep for large codebases?
Yes. ripgrep (rg) is significantly faster because it uses Rust's regex engine, respects .gitignore by default, and searches in parallel. Install it with apt install ripgrep, dnf install ripgrep, or pacman -S ripgrep.
How do I search inside gzip-compressed log files?
Use zgrep instead of grep. It decompresses on the fly and accepts the same flags. For other formats, bzgrep handles bzip2 and xzgrep handles xz-compressed files.
Can I use grep to search only specific file types in a recursive scan?
Yes. Use --include='*.py' to restrict matches to files matching a glob pattern. You can specify --include multiple times and combine it with --exclude-dir to skip directories like node_modules or .git.

Related guides