Regular expressions (“regexes”) allow defining a pattern and executing it against strings. Substrings which match the pattern are termed “matches”.
A regular expression is a sequence of characters that define a search pattern.
Regex finds utility in:
input validation
find-replace operations
advanced string manipulation
file search or rename
whitelists and blacklists
…
Simultaneously, regular expressions are ill-suited for other kinds of problems:
parsing XML or HTML
exactly matching dates
…
There are several regex implementations—regex engines—each with its own quirks and features. This book will avoid going into the differences between these, instead sticking to features that are, for the most part, common across engines.
The example blocks throughout the book use JavaScript under the hood. As a result, the book may be slightly biased towards JavaScript’s regex engine.
Basics
Regular expressions are typically formatted as /<rules>/<flags>. Often people will drop the slashes and flags for brevity. We’ll get into the details of flags in a later chapter.
Let’s start with the regex /p/g. For now, please take the g flag for granted.
As we can see, /p/g matches all lowercase p characters.
Note
Regular expressions are case-sensitive by default.
Instances of the regex pattern found in an input string are termed “matches”.
Character Classes
It’s possible to match a character from within a set of characters.
/[aeiou]/g matches all vowels in our input strings.
Here’s another example of these in action:
We match a p, followed by one of the vowels, followed by a t.
There’s an intuitive shortcut for matching a character from within a continuous range.
Warning
The regex /[a-z]/g matches only one character. In the example above, the strings have several matches each, each one character long. Not one long match.
We can combine ranges and individual characters in our regexes.
Our regex /[A-Za-z0-9_-]/g matches a single character, which must be (at least) one of the following:
from A-Z
from a-z
from 0-9
one of _ and -.
We can also “negate” these rules:
The only difference between the first regex of this chapter and /[^aeiou]/g is the ^ immediately after the opening bracket. Its purpose is to negate the rules defined within the brackets. We are now saying:
“match any character that is not any of a, e, i, o, and u”
Examples
Prohibited username characters
Unambiguous characters
Character Escapes
Character escapes act as shorthands for some common character classes.
Digit character — \d
The character escape \d matches digit characters, from 0 to 9. It is equivalent to the character class [0-9].
\D is the negation of \d and is equivalent to [^0-9].
Word character — \w
The escape \w matches characters deemed “word characters”. These include:
lowercase alphabet — a–z
uppercase alphabet — A–Z
digits — 0–9
underscore — _
It is thus equivalent to the character class [a-zA-Z0-9_].
Whitespace character — \s
The escape \s matches whitespace characters. The exact set of characters matched is dependent on the regex engine, but most include at least:
space
tab — \t
carriage return — \r
new line — \n
form feed — \f
Many also include vertical tabs (\v). Unicode-aware engines usually match all characters in the separator category.
The technicalities, however, will usually not be important.
Any character — .
While not a typical character escape, . matches any1 character.
Except the newline character \n. This can be changed using the “dotAll” flag, if supported by the regex engine in question.↩
Escapes
In regex, some characters have special meanings as we will explore across the chapters:
We’ll see how to do a lot of this in later chapters, but learning how groups work will allow us to study some great examples in these later chapters.
Capturing groups
Capturing groups are denoted by ( … ). Here’s an expository example:
Capturing groups allow extracting parts of matches.
Using your language’s regex functions, you would be able to extract the text between the matched braces for each of these strings.
Capturing groups can also be used to group regex parts for ease of repetition of said group. While we will cover repetition in detail in chapters that follow, here’s an example that demonstrates the utility of groups.
Other times, they are used to group logically similar parts of the regex for readability.
Backreferences
Backreferences allow referring to previously captured substrings.
The match from the first group would be \1, that from the second would be \2, and so on…
Backreferences cannot be used to reduce duplication in regexes. They refer to the match of groups, not the pattern.
Here’s an example that demonstrates a common use-case:
This cannot be achieved with a repeated character classes.
Non-capturing groups
Non-capturing groups are very similar to capturing groups, except that they don’t create “captures”. They take the form (?: … ).
Non-capturing groups are usually used in conjunction with capturing groups. Perhaps you are attempting to extract some parts of the matches using capturing groups. You may wish to use a group without messing up the order of the captures. This is where non-capturing groups come handy.
Examples
Query String Parameters
We match the first key-value pair separately because that allows us to use &, the separator, as part of the repeating group.
(Basic) HTML tags
As a rule of thumb, do not use regex to match XML/HTML.1234
This is a paragraph with some words.
Some instances of the word "word" are in their plural form: "words".
Yet, some are in their singular form: "word".
After
This is a paragraph with some phrases.
Some instances of the phrase "phrase" are in their plural form: "phrases".
Yet, some are in their singular form: "phrase".
In replacement contexts, $1, $2, … are usually used in place of \1, \2, … to refer to captured strings.↩
Repetition
Repetition is a powerful and ubiquitous regex feature. There are several ways to represent repetition in regex.
Making things optional
We can make parts of regex optional using the ? operator.
Here’s another example:
Here the s following http is optional.
We can also make capturing and non-capturing groups optional.
Zero or more
If we wish to match zero or more of a token, we can suffix it with *.
Our regex matches even an empty string "".
One or more
If we wish to match one or more of a token, we can suffix it with a +.
Exactly x times
If we wish to match a particular token exactly x times, we can suffix it with {x}. This is functionally identical to repeatedly copy-pasting the token x times.
Here’s an example that matches an uppercase six-character hex colour code.
Here, the token {6} applies to the character class [0-9A-F].
Between min and max times
If we wish to match a particular token between min and max (inclusive) times, we can suffix it with {min,max}.
Warning
There must be no space after the comma in {min,max}.
At least x times
If we wish to match a particular token at least x times, we can suffix it with {x,}. Think of it as {min,max}, but without an upper bound.
A note on greediness
Regular expressions, by default, are greedy. They attempt to match as much as possible.
Suffixing a repetition operator (?, *, +, …) with a ?, one can make it “lazy”.
Here, this could also be achieved by using [^"] instead of . (as is best practice).
[…] Lazy will stop as soon as the condition is satisfied, but greedy means it will stop only once the condition is not satisfied any more
The multiline flag has to do with the regex’s handling of anchors when dealing with “multiline” strings—strings that include newlines (\n). By default, the regex /^foo$/ would match only "foo".
We might want it to match foo when it is in a line by itself in a multiline string.
Let’s take the string "bar\nfoo\nbaz" as an example:
bar
foo
baz
Without the multiline flag, the string above would be considered as a single line bar\nfoo\nbaz for matching purposes. The regex ^foo$ would thus not match anything.
With the multiline flag, the input would be considered as three “lines”: bar, foo, and baz. The regex ^foo$would match the line in the middle—foo.
Dot-all (s)
Limited Support
JavaScript, prior to ES2018, did not support this flag. Ruby does not support the flag, instead using m for the same.
The . typically matches any character except newlines. With the dot-all flag, it matches newlines too.
Unicode (u)
In the presence of the u flag, the regex and the input string will be interpreted in a unicode-aware way. The details of this are implementation-dependent, but here are some things to expect:
The use of some features like unicode codepoint escapes and unicode property escapes may be enabled.
Whitespace extended (x)
When this flag is set, whitespace in the pattern is ignored (unless escaped or in a character class). Additionally, characters following # on any line are ignored. This allows for comments and is useful when writing complex patterns.
Here’s an example from Advanced Examples, formatted to take advantage of the whitespace extended flag:
^ # start of line
(
[+-]? # sign
(?=\.\d|\d) # don't match `.`
(?:\d+)? # integer part
(?:\.?\d*) # fraction part
)
(?: # optional exponent part
[eE]
(
[+-]? # optional sign
\d+ # power
)
)?
$ # end of line
Anchors
Anchors do not match anything by themselves. Instead, they place restrictions on where matches may appear—“anchoring” matches.
You could also think about anchors as “invisible characters”.
Beginning of line — ^
Marked by a caret (^) at the beginning of the regex, this anchor makes it necessary for the rest of the regex to match from the beginning of the string.
You can think of it as matching an invisible character always present at the beginning of the string.
End of line — $
This anchor is marked by a dollar ($) at the end of the regex. It is analogous to the beginning of the line anchor.
You can think of it as matching an invisible character always present at the end of the string.
The ^ and $ anchors are often used in conjunction to ensure that the regex matches the entirety of the string, rather than merely a part.
Let’s revisit an example from Repetition, and add the two anchors at the ends of the regex.
In the absence of the anchors, http/2 and shttp would also match.
Word boundary — \b
A word boundary is a position between a word character and a non-word character.
The word boundary anchor, \b, matches an imaginary invisible character that exists between consecutive word and non-word characters.
Note
Words characters include a-z, A-Z, 0-9, and _.
There is also a non-word-boundary anchors: \B.
As the name suggests, it matches everything apart from word boundaries.
Tip
^…$ and \b…\b are common patterns and you will almost always need one or the other to prevent accidental matches.
Examples
Trailing whitespace
Markdown headings
Without anchors:
Lookaround
Note
This section is a Work In Progress.
Lookarounds can be used to verify conditions, without matching any text.
You’re only looking, not moving.
Lookahead
Positive — (?=…)
Negative — (?!…)
Lookbehind
Positive — (?<=…)
Negative — (?<!…)
Lookahead
Positive
Note how the character following the _ isn’t matched. Yet, its nature is confirmed by the positive lookahead.
After (?=[aeiou]), the regex engine hasn’t moved and checks for (?=\1) starting after the _.
Negative
Without the anchors, this will match the part without the # in each test case.
Negative lookaheads are commonly used to prevent particular phrases from matching.
Lookbehind
Limited Support
JavaScript, prior to ES2018, did not support this flag.
Positive
Negative
Examples
Password validation
Lookarounds can be used verify multiple conditions.
Quoted strings
Without lookaheads, this is the best we can do:
Advanced Examples
Javascript comments
[\s\S] is a hack to match any character including newlines. We avoid the dot-all flag because we need to use the ordinary . for single-line comments.