Regular Expressions Explained: How Regex Patterns Work

By 

Updated on

8 min read

Regex matching lens highlighting text and regular expression symbols

Sooner or later every command line session runs into a matching problem that plain text search cannot solve: find lines that start with a number, pull every email address out of a log, or replace dates in one format with another. Regular expressions (regex) are the pattern language that solves these problems, and the same core syntax works in grep, sed, awk, text editors, and most programming languages.

This guide explains the building blocks of regular expressions, anchors, character classes, quantifiers, grouping, and alternation, and shows how the same pattern carries from grep to sed and awk.

What a Regular Expression Is

A regular expression is a pattern that describes a set of strings. Instead of matching one literal word, a pattern like ^error [0-9]+ matches any line that starts with “error” followed by a space and one or more digits.

The examples in this guide use GNU grep -E (extended regular expressions), so every pattern works as written. You can test any of them by piping text into grep :

Terminal
echo "error 404" | grep -E '^error [0-9]+'
output
error 404

The line prints because it matches the pattern. When there is no match, grep prints nothing and returns a non-zero exit status. Always single-quote the pattern so the shell does not interpret characters like $ and * before grep sees them.

Literal Characters and Metacharacters

Most characters in a regex match themselves: the pattern cat matches the string “cat” anywhere in a line, including inside “concatenate”. A handful of characters have special meanings instead of matching literally:

txt
. ^ $ * + ? ( ) [ ] { } | \

These are the metacharacters, and the rest of this guide is about what they do. To match one of them literally, escape it with a backslash: \. matches a real dot, \$ a real dollar sign.

Anchors and Word Boundaries

Anchors and word boundaries do not match characters; they match positions in the line.

  • ^ - Matches the start of the line.
  • $ - Matches the end of the line.
  • \b - Matches a word boundary, the position between a word character and a non-word character.

The \b boundary is a GNU grep extension rather than part of POSIX extended regular expression syntax.

The difference is easiest to see on real input. The following input contains three similar lines, but the pattern prints only the line that consists of exactly the word “root”:

Terminal
printf 'root\nroot:x:0:0\nchroot\n' | grep -E '^root$'
output
root

Without anchors, the pattern root would also match “chroot” or a line where “root” appears in the middle. Word boundaries solve the substring problem without pinning the match to the whole line:

Terminal
echo "the cat scattered" | grep -E -o '\bcat\b'
output
cat

The -o flag prints only the matched text. Notice that “scattered” did not produce a match, because “cat” inside it is not surrounded by word boundaries.

Character Classes: Matching Sets

Square brackets match one character from a set:

  • [abc] - One character: a, b, or c.
  • [a-z] - One lowercase letter; ranges also work for [0-9] and [A-Z].
  • [^abc] - Negation: one character that is NOT a, b, or c.

For example, to match “gray” and “grey” with one pattern:

Terminal
printf 'gray\ngrey\ngroy\n' | grep -E 'gr[ae]y'
output
gray
grey

The third line does not match because “o” is not in the set. Inside brackets, most metacharacters lose their special meaning; [.] matches a literal dot.

POSIX character classes are named shortcuts that work inside brackets: [[:digit:]] is equivalent to [0-9], [[:alpha:]] matches letters, [[:space:]] matches whitespace. Many tools also support the Perl-style shorthands \d wherever PCRE is available (grep -P), but the bracket forms are the portable choice for shell work.

The Dot and Quantifiers: Matching Repetition

The dot . matches any single character except a newline. Quantifiers apply to the preceding item and control how many times it may repeat:

  • * - Zero or more times.
  • + - One or more times.
  • ? - Zero or one time (optional).
  • {n} - Exactly n times.
  • {n,m} - Between n and m times.

Combining the dot with a quantifier gives .*, which matches anything, including nothing. A more precise example matches an IPv4-looking address by requiring one to three digits in each group:

Terminal
echo "server at 192.168.1.10 is up" | grep -E -o '[0-9]{1,3}(\.[0-9]{1,3}){3}'
output
192.168.1.10

Reading it piece by piece: [0-9]{1,3} matches the first number group, \. matches a literal dot, and the parentheses with {3} repeat the dot-plus-number sequence three times.

A common beginner mistake is reaching for * when + is meant. The pattern [0-9]* happily matches an empty string, so it succeeds on every line; [0-9]+ actually requires a digit.

Grouping and Alternation

Parentheses group parts of a pattern, and the pipe | provides alternation (OR):

Terminal
printf 'error: disk full\nwarning: low memory\ninfo: started\n' | grep -E '^(error|warning):'
output
error: disk full
warning: low memory

The group limits the alternation to the two words before the colon; without parentheses, ^error|warning: would mean “starts with error, OR contains warning: anywhere”, which is rarely what you want.

Groups also capture what they match, and the captured text can be reused. In sed , \1 refers to the first group, which makes reordering text possible:

Terminal
echo "2026-01-15" | sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3.\2.\1/'
output
15.01.2026

The three groups capture the year, month, and day, and the replacement writes them back in reverse order.

Basic vs Extended Regular Expressions

POSIX defines two regex dialects, and the difference trips up almost everyone at some point. In basic regular expressions (BRE), which plain grep and sed use, the characters +, ?, |, {}, and () match literally, and you must escape them (\+, \(\)) to get the special behavior. In extended regular expressions (ERE), they are special by default.

ConstructERE (grep -E, sed -E, awk)BRE (plain grep, sed)
One or more+\+
Optional?\?
Repetition{n,m}\{n,m\}
Grouping(...)\(...\)
Alternationa|ba|b, GNU extension only

That last row is the one to watch. Alternation is not part of POSIX BRE at all, so \| works in GNU grep and GNU sed but fails silently on the BSD sed that ships with macOS.

In practice, the simplest rule is: pass -E to grep and sed and write in the extended dialect, as every example in this guide does. awk uses extended syntax natively:

Terminal
printf 'alice 92\nbob 47\ncarol 78\n' | awk '/^[ab]/ {print $1}'
output
alice
bob

The awk pattern selects lines starting with “a” or “b” and prints the first field. grep -P enables a third dialect, Perl-compatible regular expressions (PCRE), which adds features like \d and lookarounds; reach for it when the POSIX dialects run out.

One Pattern, Three Tools

The syntax is shared, but each tool wraps it differently, which is the part that usually causes confusion when moving a working pattern from one command to another. Create a small log file to follow along:

Terminal
printf 'error: disk full\nwarning: low memory\ninfo: started\n' > app.log

Because grep is a filter, the pattern is the whole job and needs nothing around it:

Terminal
grep -E '^(error|warning):' app.log

sed prints every input line unless you suppress that with -n, so selecting lines takes an address followed by an explicit p command:

Terminal
sed -E -n '/^(error|warning):/p' app.log

awk reads a bare pattern with no action block as “print the matching line”:

Terminal
awk '/^(error|warning):/' app.log
output
error: disk full
warning: low memory

All three print the same two lines and skip the “info” line. Where the tools part company is what happens after the match. grep reports lines and stops there, sed can rewrite the matched text, and awk splits each matching line into fields you can work with:

Terminal
awk -F': ' '/^(error|warning):/ {print $2}' app.log
output
disk full
low memory

Setting the field separator to ': ' makes $2 the message text, so the pattern selects the lines and the action block pulls out the part you actually wanted. This is why it pays to learn regex once as its own subject: the pattern you write today for grep is the same pattern you will paste into sed, awk, a text editor, or a Python script tomorrow.

Quick Reference

PatternMatches
.Any single character
^ / $Start / end of line
\bWord boundary in GNU grep
[abc] / [^abc]One of the set / one not in the set
[0-9], [[:digit:]]One digit
* / + / ?Zero or more / one or more / optional
{n,m}Between n and m repetitions
(foo|bar)foo or bar
\1Text captured by the first group
\.Literal dot (escaped metacharacter)

Conclusion

Anchors, character classes, quantifiers, and groups combine into patterns that handle most day-to-day matching on Linux, and the same vocabulary carries over to editors and programming languages. To put the syntax to work in specific tools, see our guides to regular expressions in grep and find and replace with sed .

Linuxize Weekly Newsletter

A quick weekly roundup of new tutorials, news, and tips.

About the authors

Dejan Panovski

Dejan Panovski

Dejan Panovski is the founder of Linuxize, an RHCSA-certified Linux system administrator and DevOps engineer based in Skopje, Macedonia. Author of 800+ Linux tutorials with 20+ years of experience turning complex Linux tasks into clear, reliable guides.

View author page