本页面有中文版。
Cette page est disponible en français.
Esta página está disponible en español.
Guides1 live hunt inside

Regex character classes: \d, \w, \s, [a-z] and their traps

Character classes are the most-used part of regex and the easiest to get subtly wrong. What [abc], [a-z], [^…], \d, \w and \s really match, and the traps behind them: [A-z], the stray hyphen, the dot inside brackets, and digits that aren't 0 to 9.

Regex Hunter logo

· 7 min read

/^[A-Z][a-z]+$/

match

  • Paris
  • Lima
  • Oslo
  • Rome

spare

  • paris
  • PARIS
  • New York
  • R2D2

A character class matches one character from a set. [aeiou] matches one vowel. [0-9] matches one digit. \s matches one space, tab or newline.

They're in almost every pattern you'll write, and most of their traps are quiet: the pattern works on the inputs you tried and lets something odd through later. This post covers what each class really matches, then the traps. Every example is live, so edit the patterns and watch the lines change.

01A set in brackets

Square brackets list the characters allowed in one position:

Matches4/4 as expected
/gr[ae]y/

Should match2/2

  • grey
  • gray

Should not2/2

  • groy
  • greay
Try it in the Playground
One character from the set, either a or e, between gr and y.

greay fails because the class fills exactly one slot. To allow more than one character, put a quantifier after it: [ae]+.

02Ranges

A hyphen between two characters means "everything in between", by character code:

Matches5/5 as expected
/^[A-Z][a-z]+$/

Should match2/2

  • Paris
  • Lima

Should not3/3

  • paris
  • PARIS
  • New York
Try it in the Playground
One capital letter, then one or more lowercase letters, and nothing else.

You can put several ranges and single characters in one class: [A-Za-z0-9_] is letters, digits and underscore.

03Negated classes

A ^ right after the opening bracket flips the set: "any character except these".

Matches5/5 as expected
/^[^aeiou]+$/

Should match3/3

  • rhythm
  • myths
  • 2024

Should not2/2

  • cat
  • queue
Try it in the Playground
Only characters that aren't lowercase vowels, from start to end.

Watch that "any character except" really means any: spaces, digits, punctuation and newlines all count. 2024 matches, because digits aren't vowels.

Outside brackets, ^ is an anchor. Inside, at the very start, it's negation. Anywhere else inside it's just a caret.

04The shorthands

Some classes are so common they have short names:

Shorthand Means (in JavaScript) Opposite
\d a digit, [0-9] \D
\w a word character, [A-Za-z0-9_] \W
\s whitespace: space, tab, newline and friends \S
. any character except a line break

Two of these surprise people. \w includes digits and the underscore, so it isn't "a letter":

Matches5/5 as expected
/^\w+$/

Should match3/3

  • hello
  • snake_case
  • R2D2

Should not2/2

  • kebab-case
  • two words
Try it in the Playground
Underscores and digits are word characters too. Hyphens and spaces aren't.

And \s is more than a space: it also matches tabs and line breaks. If you mean exactly one space, write a space.

05Trap 1: [A-z] isn't "all letters"

Ranges follow character codes. Between Z (90) and a (97) sit six other characters: [ \ ] ^ _ and the backtick. So [A-z] lets them in:

Matches4/4 as expected
/^[A-z]+$/

Should match3/3

  • Hello
  • snake_case
  • up^down

Should not1/1

  • 2024
Try it in the Playground
The last two lines match too. Underscore and caret sit between Z and a in the character table.

Write the two ranges separately: [A-Za-z].

06Trap 2: the hyphen in the middle

A hyphen between two characters makes a range, even when you meant a literal hyphen:

Matches3/3 as expected
/^[\w+-.]+$/
  • a+b
  • a.b
  • a,b
Try it in the Playground
The +-. in the middle is read as a range from + to ., which happens to include the comma.

That comma is the bug: + is code 43, . is 46, and the comma (44) sits between them. Put the hyphen first or last in the class, or escape it, and it's a plain hyphen again:

Matches4/4 as expected
/^[\w+.-]+$/

Should match3/3

  • a+b
  • a.b
  • a-b

Should not1/1

  • a,b
Try it in the Playground
Hyphen last: now it means a hyphen, and the comma is out.

07Trap 3: the dot and other specials

Most special characters lose their powers inside brackets. [.] is a literal dot, [*+?] is one of three symbols, and [$] is a dollar sign. So [.] is another way to write \.:

Matches4/4 as expected
/^\d+[.]\d+$/

Should match2/2

  • 3.14
  • 10.5

Should not2/2

  • 3x14
  • 314
Try it in the Playground
Inside brackets the dot is just a dot.

Only four characters stay special inside a class: ], \, ^ at the start, and - between two characters.

08Trap 4: digits that aren't 0 to 9

In JavaScript, \d is exactly [0-9]. In Python 3, on a normal str pattern, \d matches any Unicode digit: Arabic-Indic ٣, Devanagari ३, full-width 3. The same goes for \w, which accepts letters from any script.

import re

re.fullmatch(r"\d+", "٣٤٥")           # matches in Python
re.fullmatch(r"\d+", "٣٤٥", re.ASCII)  # None

If a number is about to go into int() or a database column, that matters. Use [0-9], or the re.ASCII flag, when you mean ASCII digits.

09Try one

Can you catch the lines made only of hex digits?

Live huntpar 13

Catch strings made only of hex digits, in either case.

Targets · 0/6 caught

  • fftarget
  • 00target
  • 1a2Btarget
  • DEADbeeftarget
  • c0ffeetarget
  • 7target

Innocents · 0 hit

  • fginnocent
  • 0x1finnocent
  • 12 34innocent
  • beef!innocent
  • gginnocent
  • zinnocent

Type a regex — the board updates as you go.

Try it in the Playground
Like this? A new one drops every day.Play today’s hunt

10Checklist

  • [abc] matches one character from the set. Add a quantifier for more.
  • [^abc] matches any character not in the set, including spaces and newlines.
  • Write [A-Za-z], never [A-z].
  • Put a literal hyphen first or last: [\w.-].
  • Inside brackets, . * + ? $ are literal.
  • \w includes digits and _. \s includes tabs and newlines.
  • In Python, \d and \w are Unicode-aware. Use [0-9] or re.ASCII for ASCII only.

11Keep practising