grep
search pattern
command line tools
Linux
programming

Can grep show only words that match search pattern?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Yes. Use grep -o to print only the matched text instead of the entire line. By default, grep outputs every line that contains a match. The -o flag changes this behavior so that each match is printed on its own line, with no surrounding context. Combined with -w for whole-word matching or -E for extended regex, grep -o covers most text extraction tasks directly from the command line.

Basic Usage: grep -o

The -o (or --only-matching) flag tells grep to output only the portion of each line that matches the pattern.

bash
echo "error code=42 warning code=99" | grep -o 'code=[0-9]*'

Output:

text
code=42
code=99

Without -o, grep would print the entire line. With -o, each match is isolated on its own line. If a single input line contains multiple matches, each one is printed separately.

bash
# From a file
grep -o 'TODO' src/main.py

Output:

text
TODO
TODO
TODO

This is useful for counting occurrences when combined with wc -l:

bash
grep -o 'TODO' src/main.py | wc -l

Whole-Word Matching: grep -ow

If you need to match complete words rather than substrings, add the -w flag. This prevents the pattern from matching inside longer words.

bash
echo "cat scatter catalog catfish cat" | grep -o 'cat'

Output without -w:

text
1cat
2cat
3cat
4cat
5cat

The pattern matches cat inside scatter, catalog, and catfish. Adding -w restricts to whole words:

bash
echo "cat scatter catalog catfish cat" | grep -ow 'cat'

Output:

text
cat
cat

Only the standalone occurrences of cat are matched.

Extended Regular Expressions: grep -oE

The -E flag enables extended regular expressions, which support +, ?, |, and () without escaping. This is essential for most real-world extraction patterns.

bash
# Extract IP addresses from a log file
grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' /var/log/auth.log
bash
# Extract email addresses
grep -oE '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' contacts.txt
bash
# Extract URLs
grep -oE 'https?://[^ ]+' README.md
bash
# Extract function names following "def " in Python
grep -oE 'def [a-zA-Z_][a-zA-Z0-9_]*' app.py

Each of these prints only the matched token, one per line, ready for piping to sort, uniq, wc, or other tools.

Combining with Other Flags

grep -o works with most other grep options:

Flag CombinationPurposeExample
-oiMatch only, case-insensitivegrep -oi 'error' app.log
-owMatch only, whole wordsgrep -ow 'cat' file.txt
-oEMatch only, extended regexgrep -oE '[0-9]+' data.csv
-oPMatch only, Perl regex (GNU grep)grep -oP '(?<=id=)\d+' log
-onMatch only, with line numbersgrep -on 'TODO' src/*.py
-ocCount matches per filegrep -oc 'error' *.log
-oRMatch only, recursive searchgrep -oR 'FIXME' src/
-ohMatch only, suppress filenamegrep -ohR 'TODO' src/

Recursive Search with Filenames

bash
1# Show matched text with filenames
2grep -oR 'TODO\|FIXME\|HACK' src/
3
4# Output:
5# src/app.js:TODO
6# src/db.js:FIXME
7# src/utils.js:HACK
8# src/utils.js:TODO

Suppressing Filenames

bash
1# Just the matches, no filenames
2grep -ohR 'TODO\|FIXME\|HACK' src/
3
4# Output:
5# TODO
6# FIXME
7# HACK
8# TODO

Practical Examples

Counting Unique Matches

bash
# Find all unique CSS class names in HTML files
grep -ohR 'class="[^"]*"' *.html | sort | uniq -c | sort -rn

Extracting Version Numbers

bash
echo "Upgraded from v1.2.3 to v2.0.0-beta.1" | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+[a-zA-Z0-9.-]*'

Output:

text
v1.2.3
v2.0.0-beta.1

Finding All Import Paths in JavaScript

bash
grep -ohRE "from '[^']+'" src/ | sort -u

Output:

text
1from './components/Button'
2from './utils/api'
3from 'react'
4from 'react-router-dom'

Extracting JSON Keys

bash
grep -oE '"[a-zA-Z_]+":' config.json | tr -d '":' | sort -u

Building a Frequency Table

bash
# Most common HTTP status codes in access log
grep -oE 'HTTP/[0-9.]+" [0-9]+' access.log | grep -oE '[0-9]+$' | sort | uniq -c | sort -rn

Output:

text
1  14523 200
2   2341 304
3    892 404
4    156 500
5     42 301

Perl-Compatible Regular Expressions: grep -oP

GNU grep supports Perl-compatible regular expressions (PCRE) with the -P flag. This adds support for lookahead, lookbehind, and non-capturing groups.

bash
# Extract the value after "id=" without including "id=" in the output
echo "user id=12345 role=admin" | grep -oP '(?<=id=)\d+'

Output:

text
12345

The (?<=id=) is a lookbehind assertion. It requires id= to precede the match but does not include it in the output. This is the closest grep gets to capture group extraction.

bash
# Extract domain from URLs
echo "https://api.example.com/v2/users" | grep -oP '(?<=://)[^/]+'

Output:

text
api.example.com

Note: -P is a GNU grep extension. It is not available on macOS grep by default. On macOS, install GNU grep via Homebrew (brew install grep) and use ggrep -oP.

When grep -o Is Not Enough

grep -o prints the entire match. It does not support printing only a capture group (a parenthesized subexpression). If you need to extract a specific part of a larger pattern, you have several options:

bash
# Goal: extract only the port number from "host:port" patterns
# grep -o matches the whole pattern, not just the port
echo "server=db.example.com:5432" | grep -oE ':[0-9]+' | tr -d ':'

For more complex extractions, sed, awk, or perl are better tools:

bash
1# Using sed to extract a capture group
2echo "server=db.example.com:5432" | sed -n 's/.*:\([0-9]*\)/\1/p'
3
4# Using awk with field splitting
5echo "server=db.example.com:5432" | awk -F: '{print $2}'
6
7# Using perl for named captures
8echo "server=db.example.com:5432" | perl -nle 'print $1 if /:(\d+)/'

Tool Comparison

Capabilitygrep -ogrep -oPsedawkperl
Print full matchYesYesYesYesYes
Capture groupsNoLookbehind onlyYesYesYes
Multiple captures per lineYes (one per match)YesLimitedYesYes
Speed on large filesFastFastModerateModerateModerate
PortabilityAll UnixGNU onlyAll UnixAll UnixMost Unix

Common Pitfalls

  • Forgetting -o and getting full lines instead of just the matched text. This is the most common mistake.
  • Using a loose pattern that matches substrings inside larger words when -w is needed. grep -o 'log' matches inside blog, catalog, and login.
  • Expecting grep -o to print only a parenthesized capture group. It prints the entire regex match. Use grep -oP with lookbehind, or switch to sed/awk/perl.
  • Relying on -P (Perl regex) in scripts that must run on macOS or BSD systems. The -P flag is GNU-specific. Use -E for portable scripts.
  • Writing an overly broad pattern that produces unexpected partial matches. For example, grep -oE '[a-z]+' on English text prints every word as individual matches, which may not be the intent.
  • Forgetting that -o changes the output format when piping to wc -l. Without -o, wc -l counts matching lines. With -o, it counts individual matches, which can be higher.

Summary

  • grep -o prints only the matched text, not the full line. Each match appears on its own line.
  • Add -w for whole-word matching to prevent substring matches inside larger words.
  • Add -E for extended regex support (+, ?, |, () without escaping).
  • Use -P with lookbehind assertions for capture-group-like extraction (GNU grep only).
  • Combine -o with -R for recursive search, -i for case-insensitive matching, and -h to suppress filenames.
  • Switch to sed, awk, or perl when you need to extract a specific subgroup from a larger match pattern.

Course illustration
Course illustration

All Rights Reserved.