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.
| Anchor | Asserts |
|---|---|
^ | 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 |
\A | start of string, always, ignores m |
\z | very end of string, always (PCRE, Java, Ruby, Go). Python has no \z |
\Z | end of string allowing one trailing newline (PCRE, Java, Ruby). In Python \Z is the strict end, the \z of every other flavor |
\G | where 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
| Flag | Changes |
|---|---|
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.
| Pattern | Input | Result |
|---|---|---|
\bcat\b | a cat. | matches cat |
\bcat\b | concat | no match |
\Bcat | concat | matches cat |
\bcat | cat-dog | matches |
cat\b | cats | no 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.
| Goal | Pattern |
|---|---|
| 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.