指南内含 1 场可玩狩猎

正则表达式字符类:\d、\w、\s、[a-z] 及其陷阱

字符类是正则中最常用的部分,也最容易出现不易察觉的错误。本文讲解 [abc]、[a-z]、[^…]、\d、\w 和 \s 到底匹配什么,以及它们背后的陷阱:[A-z]、放错位置的连字符、方括号里的点,以及不是 0 到 9 的数字。

Regex Hunter 标志

· 阅读约 6 分钟

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

抓住

  • Paris
  • Lima
  • Oslo
  • Rome

放过

  • paris
  • PARIS
  • New York
  • R2D2

字符类匹配集合中的一个字符。[aeiou] 匹配一个元音字母,[0-9] 匹配一个数字,\s 匹配一个空格、制表符或换行符。

你写的几乎每个正则里都有字符类,而它们的陷阱大多很隐蔽:正则在你试过的输入上运行正常,之后却放进了一些奇怪的东西。本文先讲每种字符类到底匹配什么,再讲陷阱。每个示例都可以实时编辑,修改正则就能看到各行的变化。

01方括号里的集合

方括号列出一个位置上允许出现的字符:

匹配4/4 符合预期
/gr[ae]y/

应当匹配2/2

  • grey
  • gray

不应匹配2/2

  • groy
  • greay
在沙盒里试试
在 gr 和 y 之间,从集合中取一个字符:a 或 e。

greay 不匹配,因为字符类只占一个位置。要允许多个字符,就在它后面加一个量词:[ae]+。

02范围

两个字符之间的连字符表示“按字符编码,介于两者之间的所有字符”:

匹配5/5 符合预期
/^[A-Z][a-z]+$/

应当匹配2/2

  • Paris
  • Lima

不应匹配3/3

  • paris
  • PARIS
  • New York
在沙盒里试试
一个大写字母,后面跟一个或多个小写字母,除此之外没有别的。

一个字符类里可以放多个范围和单个字符:[A-Za-z0-9_] 表示字母、数字和下划线。

03否定字符类

紧跟在左方括号后面的 ^ 会反转集合:“除这些以外的任意字符”。

匹配5/5 符合预期
/^[^aeiou]+$/

应当匹配3/3

  • rhythm
  • myths
  • 2024

不应匹配2/2

  • cat
  • queue
在沙盒里试试
从头到尾只有不是小写元音字母的字符。

注意,“除这些以外的任意字符”真的是任意字符:空格、数字、标点和换行符都算。2024 能匹配,因为数字不是元音字母。

在方括号外,^ 是锚点。在方括号内的最开头,它表示否定。在方括号内的其他位置,它就只是一个脱字符。

04简写

有些字符类太常用了,所以有简短的写法:

简写 含义(JavaScript 中) 反义
\d 一个数字,[0-9] \D
\w 一个单词字符,[A-Za-z0-9_] \W
\s 空白字符:空格、制表符、换行符等 \S
. 除换行符以外的任意字符

其中两个常让人意外。\w 包含数字和下划线,所以它并不等于“一个字母”:

匹配5/5 符合预期
/^\w+$/

应当匹配3/3

  • hello
  • snake_case
  • R2D2

不应匹配2/2

  • kebab-case
  • two words
在沙盒里试试
下划线和数字也是单词字符。连字符和空格不是。

而 \s 也不只是空格:它还匹配制表符和换行符。如果你只想要一个空格,就直接写一个空格。

05陷阱 1:[A-z] 不是“所有字母”

范围按字符编码计算。在 Z(90)和 a(97)之间还有六个其他字符:[ \ ] ^ _ 和反引号。所以 [A-z] 会把它们也放进来:

匹配4/4 符合预期
/^[A-z]+$/

应当匹配3/3

  • Hello
  • snake_case
  • up^down

不应匹配1/1

  • 2024
在沙盒里试试
最后两行也能匹配。在字符表中,下划线和脱字符位于 Z 和 a 之间。

把两个范围分开写:[A-Za-z]。

06陷阱 2:中间的连字符

两个字符之间的连字符会构成范围,即使你本意只是一个普通的连字符:

匹配3/3 符合预期
/^[\w+-.]+$/
  • a+b
  • a.b
  • a,b
在沙盒里试试
中间的 +-. 被当作从 + 到 . 的范围,而这个范围恰好包含逗号。

那个逗号就是 bug:+ 的编码是 43,. 是 46,逗号(44)正好在两者之间。把连字符放在字符类的开头或末尾,或者转义它,它就又变回普通的连字符了:

匹配4/4 符合预期
/^[\w+.-]+$/

应当匹配3/3

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

不应匹配1/1

  • a,b
在沙盒里试试
连字符放在末尾:现在它表示连字符本身,逗号被排除在外。

07陷阱 3:点和其他特殊字符

大多数特殊字符在方括号内都会失去特殊含义。[.] 是字面上的点,[*+?] 是三个符号中的一个,[$] 是美元符号。所以 [.] 是 \. 的另一种写法:

匹配4/4 符合预期
/^\d+[.]\d+$/

应当匹配2/2

  • 3.14
  • 10.5

不应匹配2/2

  • 3x14
  • 314
在沙盒里试试
在方括号内,点就只是点。

在字符类内部,只有四个字符仍然有特殊含义:]、\、开头的 ^,以及两个字符之间的 -。

08陷阱 4:不是 0 到 9 的数字

在 JavaScript 中,\d 就是 [0-9]。在 Python 3 中,对于普通的 str 正则,\d 匹配任何 Unicode 数字:阿拉伯-印度数字 ٣、天城文数字 ३、全角数字 3。\w 也是如此,它接受任何文字的字母。

import re

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

如果这个数字接下来要传给 int() 或存进数据库字段,这一点就很重要。想要 ASCII 数字时,请使用 [0-9] 或 re.ASCII 修饰符。

09试一试

你能抓住只由十六进制数字组成的行吗?

可玩狩猎标准杆 13

抓住只由十六进制数字组成的字符串,大小写均可。

目标 · 抓住 0/6

  • ff目标
  • 00目标
  • 1a2B目标
  • DEADbeef目标
  • c0ffee目标
  • 7目标

无辜词 · 误抓 0

  • fg无辜
  • 0x1f无辜
  • 12 34无辜
  • beef!无辜
  • gg无辜
  • z无辜

输入正则——棋盘会实时更新。

在沙盒里试试
喜欢吗?每天都有新的狩猎。玩今天的狩猎

10检查清单

  • [abc] 匹配集合中的一个字符。要匹配更多,就加量词。
  • [^abc] 匹配不在集合中的任意字符,包括空格和换行符。
  • 写 [A-Za-z],永远不要写 [A-z]。
  • 字面连字符放在开头或末尾:[\w.-]。
  • 在方括号内,. * + ? $ 都是字面字符。
  • \w 包含数字和 _。\s 包含制表符和换行符。
  • 在 Python 中,\d 和 \w 支持 Unicode。只要 ASCII 时,用 [0-9] 或 re.ASCII。

11继续练习