Regular Expressions For Regular Folk

Introduction

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:

Simultaneously, regular expressions are ill-suited for other kinds of problems:

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.

/p/g
  • 1 matchpancake
  • 3 matchespineapple
  • 2 matchesapple
  • 0 matchesmango
  • 0 matchesPlum

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

/pp/g
  • 1 matchapple
  • 1 matchpineapple
  • 1 matchhappiness
  • 2 matchessipping apple juice
  • 0 matchespapaya

Character Classes

It’s possible to match a character from within a set of characters.

/[aeiou]/g
  • 4 matchesavocado
  • 2 matchesbrinjal
  • 3 matchesonion
  • 0 matchesrhythm

/[aeiou]/g matches all vowels in our input strings.

Here’s another example of these in action:

/p[aeiou]t/g
  • 1 matchpat
  • 1 matchpet
  • 1 matchpit
  • 1 matchspat
  • 2 matchesspot a pet
  • 0 matchesbat

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.

/[a-z]/g
  • 5 matchesjohn_s
  • 5 matchesmatej29
  • 5 matchesAyesha?!
  • 0 matches4952
  • 0 matchesLOUD
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.

/[A-Za-z0-9_-]/g
  • 6 matchesjohn_s
  • 7 matchesmatej29
  • 6 matchesAyesha?!
  • 4 matches4952
  • 4 matchesLOUD

Our regex /[A-Za-z0-9_-]/g matches a single character, which must be (at least) one of the following:

We can also “negate” these rules:

/[^aeiou]/g
  • 6 matchesUmbrella
  • 6 matchescauliflower
  • 0 matchesou

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

/[^a-zA-Z_0-9-]/g
  • 0 matchesTheLegend27
  • 0 matchesWaterGuy12
  • 0 matchesSmokie_Bear
  • 7 matchesRobert'); DROP TABLE Students;--

Unambiguous characters

/[A-HJ-NP-Za-kmnp-z2-9]/g
  • 1 matchfoo
  • 2 matcheslily
  • 0 matcheslI0O1
  • 11 matchesunambiguity

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/g
  • 4 matches2020
  • 6 matches100/100
  • 3 matchesIt costs $5.45
  • 6 matches3.14159
/\d\d/g
  • 2 matches2020
  • 2 matches100/100
  • 1 matchIt costs $5.45
  • 2 matches3.14159
Note

While 59 is also a pair of digits, most engines look for non-overlapping matches from left to right by default.

\D is the negation of \d and is equivalent to [^0-9].

/\D/g
  • 0 matches2020
  • 1 match100/100
  • 11 matchesIt costs $5.45
  • 1 match3.14159

Word character — \w

The escape \w matches characters deemed “word characters”. These include:

It is thus equivalent to the character class [a-zA-Z0-9_].

/\w/g
  • 6 matchesjohn_s
  • 7 matchesmatej29
  • 6 matchesAyesha?!
  • 4 matches4952
  • 4 matchesLOUD
  • 4 matcheslo-fi
  • 6 matchesget out
  • 6 matches21*2 = 42(1)
/\W/g
  • 0 matchesjohn_s
  • 2 matchesAyesha?!
  • 0 matches4952
  • 0 matchesLOUD
  • 1 matchlo-fi
  • 1 matchget out
  • 3 matches;-;
  • 6 matches21*2 = 42(1)

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:

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.

/\s/g
  • 1 matchword word
  • 2 matchestabs vs spaces
  • 0 matchessnake_case.jpg
/\S/g
  • 8 matchesword word
  • 12 matchestabs vs spaces
  • 14 matchessnake_case.jpg

Any character — .

While not a typical character escape, . matches any1 character.

/./g
  • 6 matchesjohn_s
  • 8 matchesAyesha?!
  • 4 matches4952
  • 4 matchesLOUD
  • 5 matcheslo-fi
  • 7 matchesget out
  • 3 matches;-;
  • 12 matches21*2 = 42(1)

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


When we wish to match these characters literally, we need to “escape” them.

This is done by prefixing the character with a \.


/\(paren\)/g
  • 0 matchesparen
  • 0 matchesparents
  • 1 match(paren)
  • 1 matcha (paren)
/(paren)/g
  • 1 matchparen
  • 1 matchparents
  • 1 match(paren)
  • 1 matcha (paren)

/example\.com/g
  • 1 matchexample.com
  • 1 matcha.example.com/foo
  • 0 matchesexample_com
  • 0 matchesexample@com
  • 0 matchesexample_com/foo
/example.com/g
  • 1 matchexample.com
  • 1 matcha.example.com/foo
  • 1 matchexample_com
  • 1 matchexample@com
  • 1 matchexample_com/foo

/A\+/g
  • 1 matchA+
  • 1 matchA+B
  • 1 match5A+
  • 0 matchesAAA
/A+/g
  • 1 matchA+
  • 1 matchA+B
  • 1 match5A+
  • 1 matchAAA

/worth \$5/g
  • 1 matchworth $5
  • 1 matchworth $54
  • 1 matchnot worth $5
/worth $5/g
  • 0 matchesworth $5
  • 0 matchesworth $54
  • 0 matchesnot worth $5

Examples

JavaScript in-line comments

/\/\/.*/g
  • 1 matchconsole.log(); // comment
  • 1 matchconsole.log(); // // comment
  • 0 matchesconsole.log();

Asterisk-surrounded substrings

/\*[^\*]*\*/g
  • 1 matchhere be *italics*
  • 1 matchpermitted**
  • 1 matcha*b*c*d
  • 2 matchesa*b*c*d*e
  • 0 matchesa️bcd

The first and last asterisks are literal since they are escaped — \*.

The asterisk inside the character class does not necessarily need to be escaped1, but I’ve escaped it anyway for clarity.

The asterisk immediately following the character class indicates repetition of the character class, which we’ll explore in chapters that follow.


  1. Many special characters that would otherwise have special meanings are treated literally by default inside character classes.

Groups

Groups, as the name suggests, are meant to be used to “group” components of regular expressions. These groups can be used to:

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:

/a(bcd)e/g
  • 1 matchabcde
  • 1 matchabcdefg?
  • 1 matchabcde

Capturing groups allow extracting parts of matches.

/\{([^{}]*)\}/g
  • 1 match{braces}
  • 2 matches{two} {pairs}
  • 1 match{ {nested} }
  • 1 match{ incomplete } }
  • 1 match{}
  • 0 matches{unmatched

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.

/a(bcd)+e/g
  • 1 matchabcdefg
  • 1 matchabcdbcde
  • 1 matchabcdbcdbcdef
  • 0 matchesae

Other times, they are used to group logically similar parts of the regex for readability.

/(\d\d\d\d)-W(\d\d)/g
  • 1 match2020-W12
  • 1 match1970-W01
  • 1 match2050-W50-6
  • 1 match12050-W50

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…

/([abc])=\1=\1/g
  • 1 matcha=a=a
  • 1 matchab=b=b
  • 0 matchesa=b=c

Backreferences cannot be used to reduce duplication in regexes. They refer to the match of groups, not the pattern.

/[abc][abc][abc]/g
  • 1 matchabc
  • 1 matcha cable
  • 1 matchaaa
  • 1 matchbbb
  • 1 matchccc
/([abc])\1\1/g
  • 0 matchesabc
  • 0 matchesa cable
  • 1 matchaaa
  • 1 matchbbb
  • 1 matchccc

Here’s an example that demonstrates a common use-case:

/\w+([,|])\w+\1\w+/g
  • 1 matchcomma,separated,values
  • 1 matchpipe|separated|values
  • 0 matcheswb|mixed,delimiters
  • 0 matcheswb,mixed|delimiters

This cannot be achieved with a repeated character classes.

/\w+[,|]\w+[,|]\w+/g
  • 1 matchcomma,separated,values
  • 1 matchpipe|separated|values
  • 1 matchwb|mixed,delimiters
  • 1 matchwb,mixed|delimiters

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

/^\?(\w+)=(\w+)(?:&(\w+)=(\w+))*$/g
  • 0 matches
  • 0 matches?
  • 1 match?a=b
  • 1 match?a=b&foo=bar

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

However, it’s a relevant example:

/<([a-z]+)+>(.*)<\/\1>/gi
  • 1 match<p>paragraph</p>
  • 1 match<li>list item</li>
  • 1 match<p><span>nesting</span></p>
  • 0 matches<p>hmm</li>
  • 1 match<p><p>not clever</p></p></p>

Names

Find: \b(\w+) (\w+)\b

Replace: $2, $15

Before

John Doe
Jane Doe
Sven Svensson
Janez Novak
Janez Kranjski
Tim Joe

After

Doe, John
Doe, Jane
Svensson, Sven
Novak, Janez
Kranjski, Janez
Joe, Tim

Backreferences and plurals

Find: \bword(s?)\b

Replace: phrase$15

Before

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

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.

/a?/g
  • 1 match
  • 2 matchesa
  • 3 matchesaa
  • 4 matchesaaa
  • 5 matchesaaaa
  • 6 matchesaaaaa

Here’s another example:

/https?/g
  • 1 matchhttp
  • 1 matchhttps
  • 1 matchhttp/2
  • 1 matchshttp
  • 0 matchesftp

Here the s following http is optional.

We can also make capturing and non-capturing groups optional.

/url: (www\.)?example\.com/g
  • 1 matchurl: example.com
  • 1 matchurl: www.example.com/foo
  • 1 matchHere's the url: example.com.

Zero or more

If we wish to match zero or more of a token, we can suffix it with *.

/a*/g
  • 1 match
  • 2 matchesa
  • 2 matchesaa
  • 2 matchesaaa
  • 2 matchesaaaa
  • 2 matchesaaaaa

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

/a+/g
  • 0 matches
  • 1 matcha
  • 1 matchaa
  • 1 matchaaa
  • 1 matchaaaa
  • 1 matchaaaaa

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.

/a{3}/g
  • 0 matches
  • 0 matchesa
  • 0 matchesaa
  • 1 matchaaa
  • 1 matchaaaa
  • 1 matchaaaaa

Here’s an example that matches an uppercase six-character hex colour code.

/#[0-9A-F]{6}/g
  • 1 match#AE25AE
  • 1 match#663399
  • 1 matchHow about #73FA79?
  • 1 matchPart of #73FA79BAC too
  • 0 matches#FFF
  • 0 matches#a2ca2c

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

/a{2,4}/g
  • 0 matches
  • 0 matchesa
  • 1 matchaa
  • 1 matchaaa
  • 1 matchaaaa
  • 1 matchaaaaa
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{2,}/g
  • 0 matches
  • 0 matchesa
  • 1 matchaa
  • 1 matchaaa
  • 1 matchaaaa
  • 1 matchaaaaa

A note on greediness

Repetition operators, by default, are greedy. They attempt to match as much as possible.

/a*/g
  • 2 matchesaaa
  • 2 matchesaaaa
  • 2 matchesaaaaa

* can match zero or more instances of a. In each of the example strings, it could just as well match, say, zero as. Yet, it matches as many as it can.


/a{2,4}/g
  • 1 matchaaaaa
  • 1 matchaaa
  • 1 matchaaaa

Suffixing a repetition operator (*, +, ?, …) with a ?, one can make it “lazy”.

/a{2,4}?/g
  • 2 matchesaaaaa
  • 1 matchaaa
  • 2 matchesaaaa
Warning

The suffix ? here is different from the repetition operator ?.


/".*"/g
  • 1 match"quote"
  • 1 match"quote", "quote"
  • 1 match"quote"quote"
/".*?"/g
  • 1 match"quote"
  • 2 matches"quote", "quote"
  • 1 match"quote"quote"

Now, .* matches no more than the minimum necessary to allow a match.1

[…] 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.

Andrew S on Stack Overflow

/<.+>/g
  • 1 match<em>g r e e d y</em>
/<.+?>/g
  • 2 matches<em>lazy</em>

Examples

Bitcoin address

/([13][a-km-zA-HJ-NP-Z0-9]{26,33})/g
  • 1 match3Nxwenay9Z8Lc9JBiywExpnEFiLp6Afp8v
  • 1 match1HQ3Go3ggs8pFnXuHVHRytPCq5fGG8Hbhx
  • 1 match2016-03-09,18f1yugoAJuXcHAbsuRVLQC9TezJ

YouTube Video

/(?:https?:\/\/)?(?:www\.)?youtube\.com\/watch\?.*?v=([^&\s]+).*/gm
  • 1 matchyoutube.com/watch?feature=sth&v=dQw4w9WgXcQ
  • 1 matchhttps://www.youtube.com/watch?v=dQw4w9WgXcQ
  • 1 matchwww.youtube.com/watch?v=dQw4w9WgXcQ
  • 1 matchyoutube.com/watch?v=dQw4w9WgXcQ
  • 1 matchfakeyoutube.com/watch?v=dQw4w9WgXcQ

We can adjust this to not match the last broken link using anchors, which we shall encounter soon.


  1. In this example, we could also achieve this using /"[^"]*"/g (as is best practice).

Alternation

Alternation allows matching one of several phrases. This is more powerful than character classes, which are limited to characters.

Delimit the set of phrases with pipes—|.

/foo|bar|baz/g
  • 2 matchesfoo baz
  • 1 matchYour food
  • 1 matchBehind bars

One of foo, bar, and baz


If only a part of the regex is to be “alternated”, wrap that part with a group—capturing or non-capturing.

/Try (foo|bar|baz)/g
  • 1 matchTry foo
  • 1 matchTry bar
  • 1 matchTry baz
  • 1 matchTry food

Try followed by one of foo, bar, and baz


Matching numbers between 100 and 250:

/1\d\d|2[0-4]\d|250/g
  • 3 matches100, 157, 199
  • 2 matches139 + 140 = 279
  • 1 match201 INR
  • 1 match$220
  • 1 match250
  • 1 match1250
  • 2 matchese = 2.71828182...
  • 0 matches251
  • 0 matches729

This can be generalized to match arbitrary number ranges!

Examples

Hex colours

Let’s improve one of our older examples to also match shorthand hex colours.

/#([0-9A-F]{6}|[0-9A-F]{3})/g
  • 1 match#AE25AE
  • 1 match#663399
  • 1 matchHow about #73FA79?
  • 1 matchPart of #73FA79BAC too
  • 1 match#FFF
  • 0 matches#a2ca2c

It is important that [0-9A-F]{6} comes before [0-9A-F]{3}. Else:

/#([0-9A-F]{3}|[0-9A-F]{6})/g
  • 1 match#AE25AE
  • 1 match#663399
  • 1 matchHow about #73FA79?
  • 1 matchPart of #73FA79BAC too
  • 1 match#FFF
  • 0 matches#a2ca2c
Tip

Regex engines try alternatives from the left to the right.

Roman numerals

/^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$/g
  • 1 matchMMXX
  • 1 matchVI
  • 1 matchXX
  • 1 matchXI
  • 0 matchesIXI
  • 0 matchesVV

Flags

Flags (or “modifiers”) allow us to put regexes into different “modes”.

Flags are the part after the final / in /pattern/.

Different engines support different flags. We’ll explore some of the most common flags here.

Global (g)

All examples thus far have had the global flag. When the global flag isn’t enabled, the regex doesn’t match anything beyond the first match.

/[aeiou]/g
  • 3 matchescorona
  • 2 matchescancel
  • 0 matchesrhythm
/[aeiou]/
  • 1 matchcorona
  • 1 matchcancel
  • 0 matchesrhythm

(Case) Insensitive (i)

As the name suggests, enabling this flag makes the regex case-insensitive in its matching.

/#[0-9A-F]{6}/i
  • 1 match#AE25AE
  • 1 match#663399
  • 1 matchEven #a2ca2c?
  • 0 matches#FFF
/#[0-9A-F]{6}/
  • 1 match#AE25AE
  • 1 match#663399
  • 0 matchesEven #a2ca2c?
  • 0 matches#FFF
/#[0-9A-Fa-f]{6}/
  • 1 match#AE25AE
  • 1 match#663399
  • 1 matchEven #a2ca2c?
  • 0 matches#FFF

Multiline (m)

Limited Support

In Ruby, the m flag performs other functions.

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:

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.

/^p/g
  • 1 matchphotoshop
  • 1 matchpineapple
  • 0 matchestap
  • 0 matchesapple
  • 1 matchppap
  • 0 matchesmango

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.

/p$/g
  • 1 matchphotoshop
  • 0 matchespineapple
  • 0 matchesapple
  • 1 matchapp
  • 0 matchesPlum
  • 0 matchesmango

The ^ and $ anchors are often used in conjunction to ensure that the regex matches the entirety of the string, rather than merely a part.

/^p$/g
  • 1 matchp
  • 0 matchespi
  • 0 matchespea
  • 0 matchestarp
  • 0 matchesapple

Let’s revisit an example from Repetition, and add the two anchors at the ends of the regex.

/^https?$/g
  • 1 matchhttp
  • 1 matchhttps
  • 0 matcheshttp/2
  • 0 matchesshttp
  • 0 matchesftp

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.

/\bp/g
  • 1 matchpeach
  • 1 matchbanana, peach
  • 1 matchbanana+peach
  • 1 matchbanana-peach
  • 0 matchesbanana_peach
  • 0 matchesbanana%20peach
  • 0 matchesgrape
Note

Words characters include a-z, A-Z, 0-9, and _.

/\bp\b/g
  • 1 matchword p word
  • 1 match(p)
  • 1 matchp+q+r
  • 0 matches(paren)
  • 0 matches(loop)
  • 0 matchesloops
/\bcat\b/g
  • 1 matchcat
  • 1 matchthe cat?
  • 0 matchescatch
  • 0 matchesconcat it
  • 0 matchesconcatenate

There is also a non-word-boundary anchors: \B.

As the name suggests, it matches everything apart from word boundaries.

/\Bp/g
  • 1 matchape
  • 1 matchleap
  • 1 match(leap)
  • 0 matchesa pot
  • 0 matchespea
/\Bp\B/g
  • 1 matchape
  • 1 match_peel
  • 0 matchesleap
  • 0 matches(leap)
  • 0 matchesa pot
  • 0 matchespea
Tip

^…$ and \b…\b are common patterns and you will almost always need one or the other to prevent accidental matches.

Examples

Trailing whitespace

/\s+$/gm
  • 1 matchabc
  • 1 matchdef
  • 0 matchesabc def

Markdown headings

/^## /gm
  • 0 matches# Heading 1
  • 1 match## Heading 2
  • 0 matches### Heading 3
  • 0 matches#### Heading 4

Without anchors:

/## /gm
  • 0 matches# Heading 1
  • 1 match## Heading 2
  • 1 match### Heading 3
  • 1 match#### Heading 4

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

/_(?=[aeiou])/g
  • 1 match_a
  • 1 matche_e
  • 0 matches_f

Note how the character following the _ isn’t matched. Yet, its nature is confirmed by the positive lookahead.

/(.+)_(?=[aeiou])(?=\1)/g
  • 1 matche_e
  • 1 matchu_u
  • 1 matchuw_uw
  • 1 matchuw_uwa
  • 0 matchesf_f
  • 0 matchesa_e

After (?=[aeiou]), the regex engine hasn’t moved and checks for (?=\1) starting after the _.

/(?=.*#).*/g
  • 1 matchabc#def
  • 1 match#def
  • 1 matchabc#
  • 0 matchesabcdef

Negative

/_(?![aeiou])/g
  • 0 matches_a
  • 0 matchese_e
  • 1 match_f
/^(?!.*#).*$/g
  • 0 matchesabc#def
  • 0 matches#def
  • 0 matchesabc#
  • 1 matchabcdef

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.

/foo(?!bar)/g
  • 1 matchfoobaz
  • 0 matchesfoobarbaz
  • 0 matchesbazfoobar
/---(?:(?!---).)*---/g
  • 1 match---foo---
  • 1 match---fo-o---
  • 1 match--------

Lookbehind

Limited Support

JavaScript, prior to ES2018, did not support this flag.

Positive

Negative

Examples

Password validation

/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/
  • 0 matcheshunter2
  • 0 matcheszsofpghedake
  • 0 matcheszsofpghedak4e
  • 1 matchzSoFpghEdaK4E
  • 1 matchzSoFpg!hEd!aK4E

Lookarounds can be used verify multiple conditions.

Quoted strings

/(['"])(?:(?!\1).)*\1/g
  • 1 matchfoo "bar" baz
  • 1 matchfoo 'bar' baz
  • 1 matchfoo 'bat's' baz
  • 1 matchfoo "bat's" baz
  • 1 matchfoo 'bat"s' baz

Without lookaheads, this is the best we can do:

/(['"])[^'"]*\1/g
  • 1 matchfoo "bar" baz
  • 1 matchfoo 'bar' baz
  • 1 matchfoo 'bat's' baz
  • 0 matchesfoo "bat's" baz
  • 0 matchesfoo 'bat"s' baz

Advanced Examples

Javascript comments

/\/\*[\s\S]*?\*\/|\/\/.*/g
  • 1 matchconst a = 0; // comment
  • 1 match/* multiline */

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

24-Hour Time

/^([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$/g
  • 1 match23:59:00
  • 1 match14:00
  • 1 match23:00
  • 0 matches29:00
  • 0 matches32:32

Meta

/<Example source="(.*?)" flags="(.*?)">/gm
  • 1 match<Example source="p[aeiou]t" flags="g">
  • 1 match<Example source="s+$" flags="gm">
  • 1 match<Example source="(['"])(?:(?!\1).)*\1" flags="g">
  • 0 matches<Example source='s+$' flags='gm'>
  • 0 matches</Example>

Replace: <Example regex={/$1/$2}>

I performed this operation in commit d7a684f.

Floating point numbers

/^([+-]?(?=\.\d|\d)(?:\d+)?(?:\.?\d*))(?:[eE]([+-]?\d+))?$/g
  • 1 match987
  • 1 match-8
  • 1 match0.1
  • 1 match2.
  • 1 match.987
  • 1 match+4.0
  • 1 match1.1e+1
  • 1 match1.e+1
  • 1 match1e2
  • 1 match0.2e2
  • 1 match.987e2
  • 1 match+4e-1
  • 1 match-8.e+2
  • 0 matches.

The positive lookahead (?=\.\d|\d) ensures that the regex does not match ..

Latitude and Longitude

/^((-?|\+?)?\d+(\.\d+)?),\s*((-?|\+?)?\d+(\.\d+)?)$/g
  • 1 match30.0260736, -89.9766792
  • 1 match45, 180
  • 1 match-90.000, -180.0
  • 1 match48.858093,2.294694
  • 1 match-3.14, 3.14
  • 1 match045, 180.0
  • 1 match0, 0
  • 0 matches-90., -180.
  • 0 matches.004, .15

See also: Floating Point Numbers

MAC Addresses

/^[a-f0-9]{2}(:[a-f0-9]{2}){5}$/i
  • 1 match01:02:03:04:ab:cd
  • 1 match9E:39:23:85:D8:C2
  • 1 match00:00:00:00:00:00
  • 0 matches1N:VA:L1:DA:DD:R5
  • 0 matches9:3:23:85:D8:C2
  • 0 matchesac::23:85:D8:C2

UUID

/[\da-f]{8}-([\da-f]{4}-){3}[\da-f]{12}/i
  • 1 match123e4567-e89b-12d3-a456-426655440000
  • 1 matchc73bcdcc-2669-4bf6-81d3-e4ae73fb11fd
  • 1 matchC73BCDCC-2669-4Bf6-81d3-E4AE73FB11FD
  • 0 matchesc73bcdcc-2669-4bf6-81d3-e4an73fb11fd
  • 0 matchesc73bcdcc26694bf681d3e4ae73fb11fd

IP Addresses

/\b(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\b/g
  • 1 match9.9.9.9
  • 1 match127.0.0.1:8080
  • 1 matchIt's 192.168.1.9
  • 1 match255.193.09.243
  • 1 match123.123.123.123
  • 0 matches123.123.123.256
  • 0 matches0.0.x.0

HSL colours

Integers from 0 to 360

/^0*(?:360|3[0-5]\d|[12]?\d?\d)$/g
  • 1 match360
  • 1 match349
  • 1 match235
  • 1 match152
  • 1 match68
  • 1 match9
  • 0 matches361
  • 0 matches404

Percentages

/^(?:100(?:\.0+)?|\d?\d(?:\.\d+)?)%$/g
  • 1 match100%
  • 1 match100.0%
  • 1 match25%
  • 1 match52.32%
  • 1 match9%
  • 1 match0.5%
  • 0 matches100.5%
  • 0 matches42

Bringing it all together

/^hsl\(\s*0*(?:360|3[0-5]\d|[12]?\d?\d)\s*(?:,\s*0*(?:100(?:\.0+)?|\d?\d(?:\.\d+)?)%\s*){2}\)$/gi
  • 1 matchhsl(0,20%,100%)
  • 1 matchHSL(0350, 002%,4.1%)
  • 1 matchhsl(360,10% , 0.2% )

Next Steps

Congratulations on getting this far!

Obligatory xkcd:

Cueball saves the day with regex

If you’d like to read more about regular expressions and how they work:

Thanks for reading!