Regular Expressions Cheatsheet

Anchors and Boundaries

Use this Regular Expressions reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Anchors

Anchors are zero-width. They assert a position and consume nothing.

AnchorAsserts
^start of string, or start of line with the m flag
$end of string, or before a final newline, or end of line with m
\Astart of string, always, ignores m
\zvery end of string, always (PCRE, Java, Ruby, Go). Python has no \z
\Zend of string allowing one trailing newline (PCRE, Java, Ruby). In Python \Z is the strict end, the \z of every other flavor
\Gwhere the previous match ended (Java, PCRE, .NET)

\A and \z are absent from JavaScript. Use ^/$ without the m flag there, or (?<![\s\S]) and (?![\s\S]) if you need a hard anchor that ignores flags.

The $ trailing-newline trap

In Python, Java, PCRE, and Ruby, $ matches before a single trailing newline as well as at the true end.

import re
bool(re.search(r'^\d+$', '42\n'))    # True, surprising
bool(re.search(r'\A\d+\Z', '42\n'))  # False, strict

In JavaScript $ is strict, so /^\d+$/.test("42\n") is false. In Go RE2 $ is also strict unless the m flag is set. When validating a whole field, strip the newline first or use the strict anchor for that flavor.

Multiline and dotall are different flags

FlagChanges
m^ and $ match at every line break
s. also matches newline

They are independent. m alone does not let . cross lines, and s alone does not make ^ match mid-string. In Python they are re.M and re.S. Go has (?m) and (?s). Ruby's /m is dotall, and Ruby's ^/$ are always multiline, which is the single most common cross-language mixup.

text = "a1\nb2"
re.findall(r'^\w\d$', text)            # []
re.findall(r'^\w\d$', text, re.M)      # ['a1', 'b2']

Word boundaries

\b sits between a \w char and a non-\w char, including at the string edges. \B is every other position.

PatternInputResult
\bcat\ba cat.matches cat
\bcat\bconcatno match
\Bcatconcatmatches cat
\bcatcat-dogmatches
cat\bcatsno match

\b inherits whatever \w means in that flavor, so it is ASCII-only in JavaScript and Unicode-aware in Python 3. That means \bcafé\b fails to behave in JavaScript, because é is not a \w char there, and the boundary lands in the middle of the word.

Hyphens and dots are boundaries. \bpart\b matches inside re-part-ition and inside a.part. For whole-token matching use explicit delimiters: (?<![\w-])part(?![\w-]).

Boundary substitutes without \b

When \b is the wrong definition, build your own with lookaround.

GoalPattern
word char neither side(?<!\w)cat(?!\w)
not part of a longer identifier(?<![\w-])cat(?![\w-])
start of string only(?<![\s\S])cat
standalone number, no decimals(?<![\d.])\d+(?![\d.])
Unicode-safe word edge in JS(?<!\p{L})cat(?!\p{L}) with u

Go RE2 has no lookaround, so wrap the token in real characters and capture the middle: (^|[^\w])cat($|[^\w]).

Matching a whole string

^...$          usual, but see the trailing-newline note
\A...\z        strict in PCRE, Java, Ruby, Go
\A...\Z        strict in Python (which has no \z)
// JavaScript: anchor both ends, no m flag
const isSlug = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
isSlug.test("hack-university");   // true
isSlug.test("hack--u");           // false

In Go, regexp.MatchString is a search, not a full match, so ^...$ is required. In Java, Matcher.matches() is already anchored to the whole input, so adding ^...$ is redundant, while find() is not anchored.