Table of Contents
- Introduction
- Module 1: Understanding and Using grep
- Why Text Filtering Tools Matter on Linux
- What grep Does
- Basic Pattern Matching with grep
- Demo: Generating Sample Text and Filtering It
- Inverting Matches with the -v Flag
- Precision Filtering on a Real Log File
- Demo: Filtering a Large syslog File
- Counting Results with wc
- Locating Matches with Line Numbers Using -n
- Case-Insensitive Searching with -i
- Chaining Multiple grep Filters Together
- Special Characters and the Need for Quoting
- Demo: Troubleshooting Quoting Problems
- Extended Regular Expressions with -E
- Demo: Using Extended Regular Expressions
- Character Classes and Ranges
- Alternation: Matching One Pattern or Another
- Repetition Ranges with Interval Expressions
- Matching Uppercase Letters and Character Ranges
- grep Flags Quick Reference
- Regular Expression Syntax Reference
- Summary
- grep Quick-Reference Cheat Sheet
Introduction
Linux systems are built around plain text. Configuration files, log entries, and a great deal of object metadata are all defined and controlled as text, even as systems increasingly rely on components like systemd and binary-backed resources such as journald logs. Because so much text moves across a Linux system — both text at rest in files and text streaming between commands — administrators need reliable tools for filtering and parsing it.
Among the oldest and most thoroughly tested of these tools is grep, a pattern-matching search utility that has been described as “a small program that does one thing well.” That description remains just as accurate today as it was more than fifty years ago. This module walks through grep from first principles — basic pattern matching, filtering real-world log files, combining grep with other commands in a pipeline, and using both basic and extended regular expressions to build precise, powerful search patterns.
Module 1: Understanding and Using grep
Why Text Filtering Tools Matter on Linux
Because Linux relies so heavily on plain text for configuration, logging, and metadata, administrators are constantly working with text that is either sitting in files or moving between programs as text streams. Manually reading through this text is impractical — a single system log file can easily contain 50,000 lines and more than 10 million characters. Purpose-built filtering tools make it possible to extract only the information that matters from data at that scale.
mindmap
root((Linux and Plain Text))
Configuration files
Logging
syslog
journald
Object metadata
Text streams
Piped command output
Files on disk
Filtering tools
grep
wc
cut
sed
What grep Does
grep is a pattern-matching search tool. Given a pattern and a source of text — either a file or a stream piped in from another command — grep returns every line that contains a match for that pattern, along with the full contents of the matching line (not just the matched characters). This line-oriented behavior is fundamental to how the tool works: grep operates on whole lines, testing each one against the supplied pattern.
flowchart LR
A[Input source] --> B{grep pattern}
A1[File] --> A
A2[Piped stream] --> A
B -->|Line matches pattern| C[Print full line to output]
B -->|Line does not match| D[Discard line]
Basic Pattern Matching with grep
The simplest use of grep is to search for a plain string with no special characters. Given a stream of text, grep scans it line by line and prints every line containing the search string.
grep is most often used at the end of a pipeline, where the pipe character (|) takes the output of one command and feeds it as input to the next. On a keyboard, the pipe character is usually accessed by holding Shift together with the backslash key.
Demo: Generating Sample Text and Filtering It
This walkthrough starts by using echo to generate a small block of sample text, then filters it with grep.
echo -e "apple\nbanana\ncherry\ngrape"
The -e flag tells echo to interpret \n as a formatting instruction (a line break) rather than as two literal characters. Running this command produces four separate lines, each containing the name of one fruit:
apple
banana
cherry
grape
Piping that output into grep filters it down to only the lines containing a given character. Recalling the previous command with the Up arrow and appending a pipe into grep a filters for any line containing the letter a:
echo -e "apple\nbanana\ncherry\ngrape" | grep a
apple
banana
grape
Three of the four fruit names contain the letter a, and those are the three lines returned. On a terminal configured with syntax highlighting, the matching characters within each line are typically highlighted in a different color, which makes it much easier to visually pick out matches from a large volume of output — assuming the highlight color is distinguishable to the viewer.
Inverting Matches with the -v Flag
Sometimes the more useful question is not “which lines contain this pattern?” but “which lines do not contain this pattern?” The -v flag inverts the match, returning only the lines where the pattern is absent.
echo -e "apple\nbanana\ncherry\ngrape" | grep -v a
cherry
Of the four fruits in the list, cherry is the only one that does not contain the letter a, so it is the only line returned.
Precision Filtering on a Real Log File
Simple string matching against a handful of lines is useful for learning the mechanics of grep, but the real value of the tool shows up when working against much larger files — for example, a typical Linux syslog file. On a system still using traditional syslog-style logging infrastructure, that file could easily contain 50,000 lines and more than 10 million characters, making it something no administrator would want to scroll through manually.
grep can be run directly against a file rather than piping file content into it with cat first (both approaches work, but running grep directly against the file is simpler):
grep error /var/log/syslog
Demo: Filtering a Large syslog File
Running grep error against the log file returns every line containing the word error. In this walkthrough, the results reveal two unrelated issues: an Opera browser session that had trouble establishing an encrypted connection to a website, and a series of errors from a Brave browser session attempting to connect to a domain called aa.online.metrix.net. A reasonable guess is that a visited website triggered background requests to that domain, and the local DNS resolver was blocking them — the kind of background noise that happens constantly behind the scenes as modern browsers operate.
To narrow the search to error lines that are not related to Brave, the search can combine a positive match for error with an inverted match for brave, chaining grep twice in a single pipeline:
grep error /var/log/syslog | grep -v brave
The -v flag on the second grep filters out any line that mentions Brave, leaving only the other error lines.
Counting Results with wc
Rather than printing potentially hundreds of matching lines directly to the screen, it is often more useful to pipe the results into wc (word count), which reports the number of lines, words, and characters in its input. This gives a fast way to gauge how much data remains after filtering, so you know whether further narrowing is needed.
grep error /var/log/syslog | grep -v brave | wc -l
262
In this example, 262 lines contain the word error but do not relate to the Brave browser. Running the equivalent command without the -v flag shows the total number of lines containing both error and brave:
grep error /var/log/syslog | grep brave | wc -l
And checking the total line count of all error references, with no second filter at all, shows the overall scope of the issue:
grep error /var/log/syslog | wc -l
412
This grep + wc -l combination is a common pattern for quickly assessing the scope of large text-based datasets — for example, getting an initial sense of how many rows in a large CSV-based dataset match a given condition, before deciding whether the result set is small enough to inspect directly.
Locating Matches with Line Numbers Using -n
Filtering text down to a manageable result set is only half the job — often you also need to go and act on those results, for example by opening a configuration file in an editor to fix or update a setting. Chaining a second filter for both error and apparmor narrows a syslog search down to just five matching lines:
grep error /var/log/syslog | grep apparmor
This highlights a general limitation of pure text filtering on large files: knowing that five lines match your criteria doesn’t tell you where those lines are. Adding the -n flag causes grep to prefix every matching line with its line number, making it much easier to jump straight to that location in a text editor:
grep error /var/log/syslog | grep -n apparmor
1842:...ALLOWED...
3017:...ALLOWED...
With -n in place, each result is prefixed with a line number and a colon, which is typically rendered in a distinct color in the terminal.
Case-Insensitive Searching with -i
Looking closely at the apparmor results, the word ALLOWED appears in uppercase in at least five places in the file. Most Linux command-line tools are case sensitive by default, so a search for the lowercase string allowed will not match the uppercase word ALLOWED:
grep error /var/log/syslog | grep -n allowed
(no results)
One option is to search for ALLOWED in uppercase directly, but that has two drawbacks: it requires deliberately toggling case, and — more importantly — you will frequently be unsure which case a target string actually uses in the file. The -i flag solves this by making the search case-insensitive, matching characters regardless of case:
grep error /var/log/syslog | grep -ni allowed
With -i added, the uppercase ALLOWED entries are found even though the search string itself was typed in lowercase.
Chaining Multiple grep Filters Together
Multiple single-letter flags can be stacked together behind one dash rather than written out separately — -ni behaves identically to -n -i. There is also no practical limit to how many grep commands can be chained together in a single pipeline. For example, filtering for lines containing both brave and apparmor can be extended with additional filters to narrow the result set further:
grep brave /var/log/syslog | grep apparmor | grep -i error
Each additional grep in the chain further restricts the result set — until, eventually, the filters become specific enough that the command returns no results at all.
sequenceDiagram
participant Log as /var/log/syslog
participant G1 as grep error
participant G2 as grep -v brave
participant WC as wc -l
Log->>G1: All log lines
G1->>G2: Lines containing "error"
G2->>WC: Lines containing "error", excluding "brave"
WC-->>WC: Count lines/words/characters
Note over WC: 262 lines (error, not brave)
Special Characters and the Need for Quoting
Pattern matching in its simplest form only works cleanly when the search pattern contains no special characters. When special characters — or characters with meaning to the shell, like spaces or quotes — appear in a search string, grep (and the shell interpreting the command) need to be told explicitly how to treat them.
A straightforward search for the string update against the syslog file returns a few dozen results, including several lines containing the phrase Can't update stage view. That short phrase contains two characters that will cause trouble if not handled carefully:
- A single quote (the apostrophe in
Can't). - A space (between
Can'tandupdate).
Demo: Troubleshooting Quoting Problems
Searching directly for the unquoted string fails and drops the shell into a hanging, unusual-looking prompt:
grep Can't update /var/log/syslog
>
The shell interprets the single quote as the start of a quoted string, and waits for a second single quote to close it — because the shell has no way of knowing that the apostrophe is meant to be part of the literal search string rather than shell quoting syntax.
Even without the single quote, the same command would still fail because of the untreated space. The shell would interpret the first word after grep as the search pattern and the second word as the name of a file or directory to search — not as two words that together form the intended search string.
The fix is to enclose the entire search string in double quotes:
grep "Can't update" /var/log/syslog
Double quotes are used here specifically because the search string itself contains a single quote; using single quotes to wrap the whole expression would conflict with that embedded apostrophe. To isolate just the apostrophe problem, searching for Can't on its own without quotes also fails:
grep Can't /var/log/syslog
>
Wrapping it in double quotes resolves it:
grep "Can't" /var/log/syslog
flowchart TD
A["Search string contains special characters?"] -->|No| B[Run grep pattern directly]
A -->|"Yes - space"| C["Quote the entire pattern\ngrep \"multi word pattern\" file"]
A -->|"Yes - single quote in pattern"| D["Use double quotes around pattern\ngrep \"Can't update\" file"]
A -->|"Yes - regex metacharacter"| E["Escape it, or rely on\nquoting to pass it through literally"]
C --> F[Pattern matched correctly]
D --> F
E --> F
Extended Regular Expressions with -E
Beyond quoting issues, grep also supports regular expressions, which let a single pattern match many different variations of text. By default grep uses Basic Regular Expressions (BRE); passing the -E flag switches it to Extended Regular Expressions (ERE), unlocking additional metacharacters like ?, +, and {n,m} without needing to escape them with a backslash. These are commonly called “regular” expressions for historical reasons, even though their behavior can feel anything but regular at first.
Demo: Using Extended Regular Expressions
Generating three lines of text, each a different spelling of the word “color”:
echo -e "colour\ncolor\ncolouur"
colour
color
colouur
The first line uses the British spelling, the second the US spelling, and the third contains a deliberate typo with two u characters. Filtering this with -E and a ? quantifier after the u matches either zero or one occurrence of the preceding character:
echo -e "colour\ncolor\ncolouur" | grep -E "colou?r"
colour
color
The ? metacharacter means “zero or one of the preceding character” — matching both the British and US spellings, but not the third line with two u characters. Using an asterisk (*) instead of ? would instead match zero or more repetitions of the preceding character, which would also match the double-u typo.
Character Classes and Ranges
A character class matches any one character from a defined set. Enclosing a range in square brackets — for example [0-9] — tells grep to match any single digit from 0 through 9, in any position within the line:
echo -e "abc\nab1c\n2b3c" | grep "[0-9]"
ab1c
2b3c
Both lines that contain at least one digit are returned, while the line containing no digits at all (abc) is excluded.
Alternation: Matching One Pattern or Another
grep can also search for multiple possible patterns using an “or” instruction. With extended regular expressions, the pipe character (|) inside the pattern acts as an alternation operator, matching either the pattern on its left or the pattern on its right:
echo -e "I have a cat\nI have a bird\nI have a fish" | grep -E "cat|bird"
I have a cat
I have a bird
This requests any line containing either cat or bird, while lines matching neither alternative (fish) are excluded.
Repetition Ranges with Interval Expressions
Extended regular expressions also support interval expressions using curly braces, {min,max}, to match a specific range of repetitions of the preceding element. Given four lines containing 1, 2, 3, and 5 repetitions of the string ha:
echo -e "ha\nhaha\nhahaha\nhahahahaha" | grep -E "(ha){2,4}"
haha
hahaha
The pattern (ha){2,4} matches between two and four repetitions of ha. The line with five repetitions still contains four consecutive ha sequences within it, so it also matches and appears in the output; only the line with a single ha is excluded.
Matching Uppercase Letters and Character Ranges
A character range such as [A-Z] matches any single uppercase letter, anywhere in the line:
echo -e "hello\nHello!\nhello world" | grep "[A-Z]"
Hello!
This returns lines containing at least one uppercase letter, regardless of whatever else appears in the line (such as an exclamation mark). Between plain-string matching, quoting, character classes, alternation, and interval expressions, these techniques cover the patterns that come up most often in day-to-day Linux administration. For anything beyond that, the grep man page (man grep) is the definitive reference for every supported option and regular-expression construct.
flowchart LR
subgraph Techniques
direction TB
T1[Plain string match]
T2[Invert with -v]
T3[Case-insensitive with -i]
T4[Line numbers with -n]
T5[Chained pipelines]
T6[Quoting special characters]
T7["Extended regex with -E"]
T8["Character classes [0-9] [A-Z]"]
T9["Alternation a|b"]
T10["Interval expressions {n,m}"]
end
T1 --> T2 --> T3 --> T4 --> T5 --> T6 --> T7 --> T8 --> T9 --> T10
grep Flags Quick Reference
| Flag | Long form | Description | Demonstrated in this course |
|---|---|---|---|
-v | --invert-match | Print only lines that do not match the pattern | Yes |
-i | --ignore-case | Perform a case-insensitive match | Yes |
-n | --line-number | Prefix each matching line with its line number in the file | Yes |
-E | --extended-regexp | Interpret the pattern as an extended regular expression (ERE) | Yes |
-e PATTERN | --regexp=PATTERN | Explicitly specify a pattern (useful when combining multiple patterns) | Referenced |
-c | --count | Print only a count of matching lines instead of the lines themselves | Reference |
-l | --files-with-matches | Print only the names of files containing at least one match | Reference |
-L | --files-without-match | Print only the names of files containing no matches | Reference |
-r, -R | --recursive | Search directories recursively | Reference |
-w | --word-regexp | Match only whole words | Reference |
-x | --line-regexp | Match only whole lines | Reference |
-o | --only-matching | Print only the matched portion of each line, not the whole line | Reference |
-A NUM | --after-context=NUM | Print NUM lines of trailing context after each match | Reference |
-B NUM | --before-context=NUM | Print NUM lines of leading context before each match | Reference |
-C NUM | --context=NUM | Print NUM lines of context on both sides of each match | Reference |
-q | --quiet / --silent | Suppress normal output; useful for checking exit status in scripts | Reference |
--color | --color=auto | Highlight matched text in the output | Implied by terminal color scheme |
Flags marked “Reference” are standard
grepoptions included here for completeness as a quick reference, in addition to the flags explicitly demonstrated in the walkthroughs above (-v,-i,-n,-E, and stacked combinations such as-ni).
Regular Expression Syntax Reference
| Syntax | Meaning | Basic Regex (BRE) | Extended Regex (ERE, -E) |
|---|---|---|---|
. | Matches any single character | Yes | Yes |
* | Zero or more of the preceding element | Yes | Yes |
^ | Anchors the match to the start of the line | Yes | Yes |
$ | Anchors the match to the end of the line | Yes | Yes |
[...] | Character class — matches any one character in the set, e.g. [0-9], [A-Z] | Yes | Yes |
[^...] | Negated character class — matches any one character not in the set | Yes | Yes |
? | Zero or one of the preceding element | Escaped as \? | Yes (unescaped) |
+ | One or more of the preceding element | Escaped as \+ | Yes (unescaped) |
{n,m} | Between n and m repetitions of the preceding element | Escaped as \{n,m\} | Yes (unescaped) |
(...) | Grouping, used with alternation or repetition | Escaped as \(...\) | Yes (unescaped) |
| | Alternation (“or”) between two patterns | Escaped as | | Yes (unescaped) |
The key practical difference between the two regex flavors is that Extended Regular Expressions (enabled with -E) let you use metacharacters like ?, +, {}, (), and | directly, while Basic Regular Expressions (the default) require those same metacharacters to be escaped with a backslash to get the equivalent special meaning.
Summary
grep is a deceptively simple tool that becomes extremely powerful once you understand how to combine it with piping, flags, and regular expressions. The core principles covered in this module:
grepfilters text line by line, printing the entire line whenever a pattern match is found anywhere in it.grepworks equally well against a file passed as an argument or against a stream piped in from another command.- The
-vflag inverts a match, returning only lines that do not contain the pattern — useful for excluding known noise from a result set. - Chaining multiple
grepcommands together in a single pipeline lets you progressively narrow down a very large result set, one filter at a time. - Piping
grepoutput intowc -lgives a fast way to gauge the scope of a filtered result set before deciding whether to inspect it directly. - The
-nflag adds line numbers to results, making it far easier to locate and edit the corresponding content in a text editor. - Linux commands are case sensitive by default; the
-iflag makes a search case-insensitive. - Search strings containing spaces or special shell characters (like single quotes) must be quoted — typically with double quotes — so the shell passes them through to
grepas a single, literal pattern. - The
-Eflag switchesgrepfrom Basic to Extended Regular Expressions, enabling unescaped use of?,+,{n,m},(), and|. - Character classes (
[0-9],[A-Z]), alternation (|), and interval expressions ({n,m}) allow a single pattern to match many different variations of text.
Mastering these fundamentals — plain matching, inversion, case sensitivity, quoting, and both flavors of regular expressions — covers the overwhelming majority of day-to-day text-filtering needs on a Linux system. For anything beyond what is covered here, the grep man page remains the definitive reference.
grep Quick-Reference Cheat Sheet
# Basic match: print all lines containing "pattern"
grep "pattern" file.txt
# Invert match: print all lines NOT containing "pattern"
grep -v "pattern" file.txt
# Case-insensitive match
grep -i "pattern" file.txt
# Show line numbers for each match
grep -n "pattern" file.txt
# Combine flags
grep -ni "pattern" file.txt
# Count matching lines instead of printing them
grep -c "pattern" file.txt
# Search recursively through a directory tree
grep -r "pattern" /path/to/dir
# Match only whole words
grep -w "pattern" file.txt
# Extended regular expressions (unescaped ? + {} () |)
grep -E "colou?r" file.txt
grep -E "cat|bird" file.txt
grep -E "(ha){2,4}" file.txt
# Character classes
grep "[0-9]" file.txt # any digit
grep "[A-Z]" file.txt # any uppercase letter
grep "[^0-9]" file.txt # any character that is NOT a digit
# Quoting a pattern containing spaces or special characters
grep "Can't update" file.txt
# Chain multiple filters to progressively narrow a result set
grep error /var/log/syslog | grep -v brave | wc -l
# Show N lines of context around each match
grep -C 3 "pattern" file.txt
Search Terms
welcome · linux · grep · command · systems · administration · networking · security · filtering · expressions · matching · ranges · regular · character · extended · matches · pattern · quoting · reference · text