Beginner

Welcome to Linux: The grep Command

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...

Table of Contents

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't and update).

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

FlagLong formDescriptionDemonstrated in this course
-v--invert-matchPrint only lines that do not match the patternYes
-i--ignore-casePerform a case-insensitive matchYes
-n--line-numberPrefix each matching line with its line number in the fileYes
-E--extended-regexpInterpret the pattern as an extended regular expression (ERE)Yes
-e PATTERN--regexp=PATTERNExplicitly specify a pattern (useful when combining multiple patterns)Referenced
-c--countPrint only a count of matching lines instead of the lines themselvesReference
-l--files-with-matchesPrint only the names of files containing at least one matchReference
-L--files-without-matchPrint only the names of files containing no matchesReference
-r, -R--recursiveSearch directories recursivelyReference
-w--word-regexpMatch only whole wordsReference
-x--line-regexpMatch only whole linesReference
-o--only-matchingPrint only the matched portion of each line, not the whole lineReference
-A NUM--after-context=NUMPrint NUM lines of trailing context after each matchReference
-B NUM--before-context=NUMPrint NUM lines of leading context before each matchReference
-C NUM--context=NUMPrint NUM lines of context on both sides of each matchReference
-q--quiet / --silentSuppress normal output; useful for checking exit status in scriptsReference
--color--color=autoHighlight matched text in the outputImplied by terminal color scheme

Flags marked “Reference” are standard grep options 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

SyntaxMeaningBasic Regex (BRE)Extended Regex (ERE, -E)
.Matches any single characterYesYes
*Zero or more of the preceding elementYesYes
^Anchors the match to the start of the lineYesYes
$Anchors the match to the end of the lineYesYes
[...]Character class — matches any one character in the set, e.g. [0-9], [A-Z]YesYes
[^...]Negated character class — matches any one character not in the setYesYes
?Zero or one of the preceding elementEscaped as \?Yes (unescaped)
+One or more of the preceding elementEscaped as \+Yes (unescaped)
{n,m}Between n and m repetitions of the preceding elementEscaped as \{n,m\}Yes (unescaped)
(...)Grouping, used with alternation or repetitionEscaped as \(...\)Yes (unescaped)
|Alternation (“or”) between two patternsEscaped 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:

  • grep filters text line by line, printing the entire line whenever a pattern match is found anywhere in it.
  • grep works equally well against a file passed as an argument or against a stream piped in from another command.
  • The -v flag inverts a match, returning only lines that do not contain the pattern — useful for excluding known noise from a result set.
  • Chaining multiple grep commands together in a single pipeline lets you progressively narrow down a very large result set, one filter at a time.
  • Piping grep output into wc -l gives a fast way to gauge the scope of a filtered result set before deciding whether to inspect it directly.
  • The -n flag 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 -i flag 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 grep as a single, literal pattern.
  • The -E flag switches grep from 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

Interested in this course?

Contact us to book it or get a custom training plan for your team.