Cybersecurity · Reference sheet

Regular Expressions (Regex)

A practical regex reference for reading logs: core tokens, character classes, quantifiers, anchors, groups, and ready-to-use IP and email patterns for grep -E.

Download Word (.docx)

What regex is and why it matters

A regular expression (regex) is a compact language for describing the *shape* of text instead of one fixed word. Rather than searching for the literal string admin, you can describe a pattern like "three digits, a dot, three digits, a dot..." to find every IP address in a file. In cybersecurity, regex is the workhorse of log analysis and threat hunting. SIEM correlation rules, IDS/IPS signatures (Snort and Suricata), firewall log parsers, and fast grep hunts all run on it. Learning to read and write patterns lets you pull every source IP out of an auth log, flag suspicious URLs, or validate input to block injection in seconds instead of scrolling. This sheet uses extended regex (ERE), the flavor you get with grep -E. Most other tools (Python re, JavaScript, Suricata) share the same core syntax, so the tokens below transfer almost everywhere.

Core tokens at a glance

These are the metacharacters that give regex its power. A character with special meaning must be escaped with a backslash to match it literally.

TokenWhat it matchesExample
.Any single character except a newlinea.c matches "abc", "a7c", "a-c"
^Start of the line (anchor, matches a position not a character)^Error matches lines starting with Error
$End of the line (anchor)failed$ matches lines ending in failed
*Zero or more of the preceding itemab* matches "a", "ab", "abbb"
+One or more of the preceding itemab+ matches "ab", "abbb" (not "a")
?Zero or one of the preceding item (makes it optional)colou?r matches "color" and "colour"
[ ]Character class: any one character listed inside[aeiou] matches any single vowel
[^ ]Negated class: any one character NOT listed[^0-9] matches any non-digit
( )Group: bundle tokens so a quantifier or | applies to all of them(ab)+ matches "ab", "abab"
|Alternation (OR) between whole expressionscat|dog matches "cat" or "dog"
{n}Exactly n repetitions of the preceding item[0-9]{3} matches exactly 3 digits, e.g. "404"
\Escape: treat the next metacharacter as a literal\. matches a literal dot, not "any character"

Character classes

A character class matches exactly one character out of a set. List the allowed characters in square brackets, use a hyphen for a range ([a-z]), and put a ^ first to negate the set ([^a-z]). The \d \w \s shorthands come from Perl/PCRE. They are convenient, but **\d is not portable to grep -E** (see the grep section), so the POSIX classes like [[:digit:]] are the safe choice in shell scripts.

ClassMatches oneExample
[a-z]Lowercase letter[a-z]+ matches "hello"
[A-Z]Uppercase letter[A-Z]{2} matches "US"
[0-9]Digit[0-9]+ matches "48122"
[a-zA-Z0-9]Letter or digit (combine ranges freely)[a-zA-Z0-9]+ matches "web01"
\dDigit, same as [0-9] (PCRE / grep -P only)\d{4} matches "2026"
\wWord character: letter, digit, or underscore\w+ matches "user_01"
\sWhitespace: space, tab, or newline\s+ matches the gap between two words
\D \W \SNegations: non-digit, non-word, non-whitespace\D+ matches "port "
[[:digit:]]POSIX class, portable in grep -E (also [[:alpha:]], [[:alnum:]], [[:space:]])[[:digit:]]{3} matches "200"

Quantifiers: how many times

A quantifier attaches to the item just before it (a character, a class, or a group) and controls how many times it may repeat.

QuantifierMeaningExample
*Zero or morelo*g matches "lg", "log", "looog"
+One or morelo+g matches "log", "looog" (not "lg")
?Zero or one (optional)https? matches "http" and "https"
{n}Exactly n times[0-9]{4} matches a 4-digit year
{n,}n or more times[0-9]{2,} matches 2 or more digits
{n,m}Between n and m times, inclusive[0-9]{1,3} matches 1 to 3 digits (one IP octet)

Quantifiers are greedy by default

By default *, +, and {n,} grab as much text as possible. Against the text <a><b>, the pattern <.*> matches the whole thing, not just <a>. Perl and grep -P let you add a ? to make a quantifier lazy (<.*?>), but POSIX grep -E has no lazy quantifiers. The portable fix is to narrow the class so it cannot cross the boundary: <[^>]*> matches a single tag.

Anchors, groups, and alternation

Anchors match a position, not a character, so they pin a pattern to where it appears. ^ ties the match to the start of the line and $ to the end. \b matches a word boundary (the edge between a word character and a non-word character), which stops \bcat\b from matching "category". Groups with ( ) let a quantifier or an alternation apply to several tokens at once, and alternation with | means "this OR that". Combine them to write one pattern that catches several cases, for example ^(ERROR|WARN|FATAL) to grab every high-severity log line.

TokenMeaningExample
^Start of line^Failed matches a line beginning with Failed
$End of line\.log$ matches a filename ending in .log
\bWord boundary (GNU grep and PCRE)\broot\b matches "root" but not "chroot"
(...)Group tokens together(ab){3} matches "ababab"
a|bMatch a or bGET|POST matches either HTTP method
(x|y)zGroup + alternation share a suffix(ssh|ftp)d matches "sshd" or "ftpd"

Two patterns worth memorizing

These are practical, log-grepping patterns. They are deliberately loose so they are short and easy to remember. They find well-formed text but do not fully validate it, for example the IP pattern will happily match 999.999.999.999. For hunting through logs that is fine. For strict input validation you would tighten each octet to (25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9]).

PatternMatchesExample match
([0-9]{1,3}\.){3}[0-9]{1,3}A dotted IPv4 address (loose)203.0.113.9
[0-9A-Fa-f]{2}(:[0-9A-Fa-f]{2}){5}A MAC address00:1a:2b:3c:4d:5e
[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}An email address (practical, not RFC-complete)will@example.com
^[0-9]{4}-[0-9]{2}-[0-9]{2}An ISO date at the start of a line2026-08-20
https?://[^ ]+An http or https URLhttps://example.com/login

Escape the dot in an IP pattern

Inside the pattern, an unescaped . means "any character", so the pattern 192.168.0.1 would also match the text "192x168y0z1". Write the literal dots as \. (as in the IP row above) so the pattern only matches real dotted addresses.

Using regex with grep -E

Plain grep uses basic regex (BRE), where +, ?, {, |, and () are literal characters and need a backslash to act special. That is confusing, so for pattern work always reach for grep -E (extended regex), where those operators just work. For the Perl \d shorthand and lookaround, use grep -P instead.

The flags you will combine most often when searching logs.

FlagWhat it doesExample
grep PATTERN fileGlobal Regular Expression Print (named after the ed editor command g/re/p) - search a file line by line and print every line that matches the pattern.grep 'Failed password' auth.log
-EExtended regex - use extended regex so +, ?, {}, |, () work without backslashesgrep -E 'error|fail' app.log
-PPerl regex - use PCRE, which enables \d, \b, and lookaroundgrep -P '\d{3}' access.log
-oonly-matching - print only the matched text, not the whole linegrep -oE '[0-9]+' access.log
-iignore case - match regardless of upper or lower casegrep -iE 'password' auth.log
-ccount - print a count of matching lines instead of the lines themselvesgrep -cE '^Failed' auth.log
-vinvert - show lines that do NOT matchgrep -vE '^#' sshd_config
-nline number - prefix each match with its line number in the filegrep -nE 'root' auth.log
-rrecursive - search every file under a directory tree, not just one filegrep -rE 'BEGIN PRIVATE KEY' /home
-llist files - print only the NAMES of files that contain a match, not the matching linesgrep -rlE 'password' /etc
-qquiet - print nothing and exit 0 as soon as a match is found (handy in shell scripts)grep -qE 'root' auth.log && echo present

\d does not work in grep -E

This trips up almost everyone. In grep and grep -E, the pattern \d matches a literal letter d, not a digit, so grep -E '\d' gives wrong results silently. Use [0-9] or [[:digit:]] in ERE, or switch to grep -P '\d'. As a quirk, GNU grep does accept \w, \s, and \b as extensions in -E, but \d is the one that fails, so prefer [0-9] to stay safe and portable.

# First, look at the raw failed-login lines
$ grep -E 'Failed password' auth.log
Aug 20 09:14:02 web01 sshd[20117]: Failed password for invalid user admin from 203.0.113.9 port 48122 ssh2
Aug 20 09:14:05 web01 sshd[20119]: Failed password for root from 198.51.100.23 port 51234 ssh2
Aug 20 09:14:07 web01 sshd[20121]: Failed password for invalid user test from 192.0.2.55 port 40551 ssh2
# Now pull just the source IPs out of the log — grep -oE prints ONLY the matched text, one match per line
$ grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' auth.log
203.0.113.9
203.0.113.9
198.51.100.23
# A clean list of every IP in the log. Counting and ranking them (203.0.113.9 shows up again and again — a brute-force signature) is a job for the Text Processing & Pipelines sheet.
Defensive use: tally failed SSH logins by source IP in an auth log

Test a pattern before you trust it

Feed a known string into grep to confirm a pattern behaves before running it across gigabytes of logs (echo just prints its text so grep has something to read). echo "203.0.113.9" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' should print the address back. If it prints nothing, your pattern is wrong, not the data. An online tester like regex101 is great for building patterns interactively.

Try it yourself

A 28-line sample application log mixing ERROR, WARN, FATAL, INFO, DEBUG, and TRACE lines, built to test the ^(ERROR|WARN|FATAL) anchor pattern and the escaped-dot IP pattern.

A short fictional contact directory for practicing grep -oE email extraction.

Get to the files first

Your browser saves both files to your Downloads folder. Move them into a labs folder and go there: mkdir -p ~/labs && mv ~/Downloads/app.log ~/Downloads/contacts.txt ~/labs/ && cd ~/labs Then run ls and confirm you can see both app.log and contacts.txt. On WSL, your Windows Downloads folder is /mnt/c/Users/<your-name>/Downloads instead of ~/Downloads.

1. Write one pattern that matches only the high-severity lines of app.log: those beginning with ERROR, WARN, or FATAL. Hint: combine the ^ anchor with alternation and a group. Run it with grep -E '<pattern>' app.log and count the matches with -c. Does the line that only mentions the word ERROR in the middle of an INFO line count? Does the line that starts with lowercase warn count? 2. Use grep -oE with the email pattern from this sheet against contacts.txt to extract every address, one per line. How many does it find — and why doesn't it pick up the line that spells the address out in words, or the line with root@localhost? 3. Near the end of app.log are two lines built to test the escaped-dot IP pattern: one address written with real dots, one with the dots swapped for x. Run the loose IP pattern from this sheet with grep -oE against the file and see which of the two gets extracted. Then explain in one sentence why an unescaped . in the pattern would have matched both.