Skip to content

正则表达式 速查表

用于模式匹配和文本处理的正则表达式。

01

入门

基础语法

正则表达式使用特殊字符进行模式匹配。. 匹配任意字符,[] 定义字符类,\d \w \s 是常用字符集的简写。在 [] 内使用 ^ 表示取反。

regex
# Basic matching
hello       # literal match
.           # any character except newline
[abc]       # any of a, b, c
[^abc]      # not a, b, or c
[a-z]       # range a to z
\d          # digit [0-9]
\D          # non-digit
\w          # word char [a-zA-Z0-9_]
\W          # non-word char
\s          # whitespace
\S          # non-whitespace

元字符

元字符具有特殊含义。要字面匹配它们,请加反斜杠前缀。在字符类 [] 内,只有 ] \ ^ 和 - 需要转义(且 - 仅在不处于开头/结尾时需要)。

regex
# Special metacharacters that need escaping:
.  *  +  ?  ^  $  {  }  [  ]  \\  |  (  )

# Escape with backslash to match literally
\.           # literal dot
\*           # literal asterisk
\+           # literal plus
\?           # literal question mark
\(           # literal parenthesis
\[           # literal bracket
\$           # literal dollar
\^           # literal caret
\\           # literal backslash

# Example: match "1.5" not "1x5"
1\.5         # matches "1.5" only

点号与通配符

点号 . 默认匹配除换行符外的任意字符。使用 s 标志(dotall 模式)可让 . 匹配包括换行符在内的所有字符。点号在字符类 [] 内是字面量,无需转义。

regex
# Dot matches any char except newline (by default)
a.c          # matches "abc", "a c", "a1c", "a@c"
a..c         # four chars: a, any, any, c

# Dot matches newline only with /s flag (dotall)
/a.b/s       # "a\nb" matches

# Match any character truly (with /s flag)
/[\s\S]/     # any char including newline (portable)
/[\d\D]/     # also any char
/[\w\W]/     # also any char

# Practical: any chars between tags
<div[\s\S]*?</div>

代码中的正则字面量

JavaScript 支持 /pattern/flags 字面量语法;Python/Java 使用字符串构造。字面量形式更易读且无需双重转义,但无法在运行时动态构建。字符串形式需要双重转义反斜杠(如 \\d)。

regex
# JavaScript - regex literal
const re = /\bword\b/gi;

# JavaScript - constructor (for dynamic patterns)
const re2 = new RegExp("\\bword\\b", "gi");

# Python - raw string with re module
import re
pattern = r"\bword\b"
re.search(pattern, "a word here")

# Java - String needs double escaping
Pattern p = Pattern.compile("\\bword\\b");

# PHP - Perl-style
if (preg_match('/\\bword\\b/i', $text)) { }

# Go - RE2 syntax
re := regexp.MustCompile(`\\bword\\b`)

测试与匹配

test() 返回布尔值,exec() 返回匹配详情(包括捕获组)。match() 在没有 g 标志时返回类 exec 结果,有 g 标志时返回所有匹配数组。matchAll() 返回迭代器,需配合 g 标志使用。

regex
# JavaScript
/abc/.test("abcdef")        # true
"abcdef".match(/abc/)       # ["abc", index: 0]
"abcabc".match(/abc/g)      # ["abc", "abc"]
"abcdef".search(/abc/)      # 0 (index)

# Python
import re
re.search(r"abc", "abcdef")  # Match object or None
re.match(r"abc", "abcdef")   # anchored at start
re.fullmatch(r"abc", "abc")  # entire string

# Test if pattern matches anywhere
bool(re.search(r"abc", "xabcx"))  # True
02

字符类

字符集合 [abc]

方括号定义字符集合,匹配其中任意一个字符。顺序无关。常见用途:元音 [aeiou]、十六进制 [0-9a-fA-F]。在集合内,大多数元字符变为字面量。

regex
# Match any single char from the set
[aeiou]      # any vowel
[abc123]     # any of a, b, c, 1, 2, 3
[ABC]        # uppercase A, B, or C (case-sensitive)

# In a set, most metacharacters are literal
[.*+]        # literal . * or +
[()]         # literal ( or )

# Quantifiers work on sets
[aeiou]{2}   # exactly two vowels
[0-9]+       # one or more digits

取反集合 [^abc]

开头的 ^ 表示取反:匹配不在集合中的任意字符。注意 [^] 在 JS 中匹配任意字符(包括换行),但这不可移植。\D \W \S 是常见取反简写。

regex
# Caret at start negates the set
[^aeiou]     # any char that is NOT a vowel
[^0-9]       # any non-digit
[^\s]        # any non-whitespace
[^a-zA-Z]    # any non-letter

# Common: match up to a delimiter
[^,]+        # everything until next comma
[^;]+        # everything until next semicolon
[^"\\]      # any char except quote or backslash

# Negation works with quantifiers
[^aeiou]+    # one or more non-vowels

范围 [a-z]

连字符 - 在字符类内表示范围。范围基于字符码点: [A-z] 会错误包含 [\]^_` 等符号。要字面匹配连字符,放于开头/结尾或转义。使用 \u{...} 支持 Unicode 范围。

regex
# Hyphen defines a range inside []
[a-z]        # lowercase letter
[A-Z]        # uppercase letter
[0-9]        # digit (same as \d)
[a-zA-Z]     # any letter
[a-zA-Z0-9]  # alphanumeric
[a-fA-F0-9]  # hexadecimal digit

# ASCII order matters
[A-z]        # WARNING: includes [ \ ] ^ _ `

# Multiple ranges in one set
[a-zA-Z0-9_]  # word character (same as \w)

# Literal hyphen: place at start or end, or escape
[-a-z]       # hyphen or lowercase letter
[a-z-]       # lowercase letter or hyphen
[a\-z]       # a, hyphen, or z

预定义简写

\d \w \s 是常见简写: \d=数字, \w=单词字符 [A-Za-z0-9_], \s=空白。它们在 ASCII 范围内等价于显式范围。使用 u 标志时,\w 仍仅匹配 ASCII,但 \d 在某些引擎中可能匹配 Unicode 数字。

regex
# Most common shorthands (work outside and inside [])
\d    # digit [0-9]
\w    # word char [a-zA-Z0-9_]
\s    # whitespace [ \t\n\r\f\v]
\D    # non-digit
\W    # non-word char
\S    # non-whitespace

# With ASCII-only flag (Python)
\d      # Unicode digits by default
\d      # [0-9] with re.ASCII flag

# Inside character classes
[\d\s]    # digit or whitespace
[\w.]     # word char or literal dot
[a-z\d]   # letter or digit

# POSIX classes (PCRE, not JS)
[[:alpha:]]  # letters
[[:digit:]]  # digits
[[:alnum:]]  # alphanumeric
[[:space:]]  # whitespace

取反简写

\D \W \S 是 \d \w \s 的取反版本。注意它们也匹配换行符。组合使用可实现否定逻辑:[^\d] 等价于 \D。在表单验证中,使用 ^\D*$ 可确保不含数字。

regex
# Uppercase versions are negations
\D    # any non-digit            [^0-9]
\W    # any non-word char        [^a-zA-Z0-9_]
\S    # any non-whitespace       [^\s]

# Useful for "match until X" patterns
\S+        # one or more non-whitespace (a word)
\D+        # one or more non-digits
\W         # a single non-word char

# Combined with anchors
^\S+       # word at start of string
\S+$       # word at end of string

# Find non-word boundaries for tokenization
\W+        # split on punctuation/space

# Caveat: \D matches newlines too
[^\d\n]    # non-digit AND non-newline

组合字符类

字符类可以组合简写和字面量:[\w-.] 匹配单词字符、连字符和点号。集合内的 - 放于末尾避免被解释为范围。使用交集(Java/PHP)与差集(JGsoft)可实现高级字符类运算。

regex
# Mix shorthands, ranges, and literals in []
[\w\s]       # word char or whitespace
[a-z\d_]     # lowercase, digit, or underscore
[\w.-]       # word char, dot, or hyphen (e.g., domain)
[^\w\s]     # neither word char nor whitespace (punctuation)

# Common practical classes
[A-Z][a-z]+       # Capitalized word
\d{1,3}\.\d{1,3}  # version-like number
[\w.+-]+@\w+     # email-ish

# Hex color
#[a-fA-F0-9]{3,6}

# Match identifier (variable name)
[a-zA-Z_$][\w$]*

# Filename with extension
[\w.-]+\.[a-zA-Z]{1,5}
03

量词

星号 *(零次或多次)

* 匹配前一项 0 次或多次。默认贪婪(尽量多匹配)。常见组合: .* 匹配任意字符串(除换行), \s* 匹配可选空白。注意 (a*)* 等嵌套量词可能导致灾难性回溯。

regex
# * matches 0 or more occurrences
ab*c        # "ac", "abc", "abbc", "abbbbc"
\d*         # zero or more digits
\s*         # optional whitespace
[a-z]*      # zero or more lowercase letters

# Useful for optional content
https?://   # http:// or https:// (note the ?)
color:\s*#?[a-f0-9]+   # color: #abc or color: abc

# * is greedy - matches as much as possible
<a.*>       # from <a to LAST > on the line

# Make it lazy with ?
<a.*?>      # from <a to FIRST > (lazy)
.*?         # zero or more, lazy

# * on group
(ab)*       # "", "ab", "abab", "ababab"

加号 +(一次或多次)

+ 匹配前一项 1 次或多次。与 * 不同,至少需要一次出现。\d+ 匹配整数,\w+ 匹配单词。在重复内容为空时,+ 不会无限循环(而 * 在某些构造中可能)。

regex
# + matches 1 or more occurrences (at least one)
ab+c        # "abc", "abbc", but NOT "ac"
\d+         # one or more digits (integer)
\w+         # one or more word chars (a word)
\s+         # one or more whitespace

# Common patterns
\w+@\w+             # simplistic email
\d+\.\d+           # decimal number
-?\d+               # optional sign + integer
[A-Z]+              # one or more uppercase

# Greedy by default - use +? for lazy
".+?"        # quoted string (lazy: stops at first ")
".+"         # greedy: matches to the LAST "

# + vs *: + requires at least one
a+           # "a", "aa", "aaa" (not "")
a*           # "", "a", "aa", "aaa"

问号 ?(零次或一次)

? 使前一项可选(0 或 1 次)。也用于使量词变懒(?、*?、+?、{n,m}?)。在分组开头有特殊含义:(?:) 非捕获组、(?=) 正向预查、(?!) 负向预查等。

regex
# ? matches 0 or 1 occurrence (optional)
colou?r     # "color" or "colour"
https?://   # "http://" or "https://"
-?\d+       # integer with optional minus sign
[+-]?\d*    # number with optional sign

# Make greedy quantifier lazy
.*?          # lazy zero-or-more
.+?          # lazy one-or-more
??           # lazy zero-or-one (rarely needed)

# Optional group
Nov(?:ember)?  # "Nov" or "November"
Mr\.?         # "Mr" or "Mr."

# Optional non-capturing group with separator
\d{4}[-/]?(\d{2})  # 2024-01 or 202401

# Chained optionals
a?b?c?       # "", "a", "ab", "abc", "b", "bc", "c"

花括号 {n,m}

{n} 精确 n 次,{n,} 至少 n 次,{n,m} 介于 n 和 m 次。{,m} 在 JS 中无效,需写为 {0,m}。量词默认贪婪,加 ? 变懒。在 PCRE 中 {n,m} 间不能有空格。

regex
# Exact count
\d{3}        # exactly 3 digits
[a-z]{5}     # exactly 5 lowercase letters
.{10}        # exactly 10 of any char

# Range: min, max
\d{2,4}      # 2 to 4 digits
\w{3,}       # 3 or more word chars
.{0,5}       # 0 to 5 of any char (same as.....?)

# Open-ended
\d{3,}       # 3 or more digits
\s{1,}       # one or more whitespace (same as \s+)
\d{0,}       # zero or more (same as \d*)

# Practical patterns
\d{4}-\d{2}-\d{2}        # date YYYY-MM-DD
\d{1,3}(?:\.\d{1,3}){3}  # IPv4 address
\d{3}-\d{3}-\d{4}        # US phone
[A-Fa-f0-9]{6}             # 6 hex digits
\d{4,16}                   # 4-16 digit PIN

# Greedy by default - add ? for lazy
\d{2,4}?    # match minimum (2)

贪婪量词

默认贪婪:尽量多匹配,再按需回溯。'<.+>' 匹配 '<a><b>' 整体而非 '<a>'。对于 HTML 等嵌套结构,贪婪常导致过度匹配,应改用懒量词或更具体的字符类。

regex
# Quantifiers are greedy by default
# They match as MUCH as possible while allowing rest to match
".*"          # in 'a "b" c "d" e', matches "b" c "d"
<a.*>         # matches from <a to the LAST >

# Greedy backtracks to allow overall match
\d+\d        # in "12345", \d+ takes "1234", last \d takes "5"

# Common greedy patterns
.*            # entire line
.+            # entire line (at least 1 char)
\d+           # all consecutive digits
\w+           # all consecutive word chars

# Greedy can be a problem with HTML
<.*>          # WRONG: matches <b>text</b> as one
<\w+>         # better: matches just <b>

# Greedy with anchored end
^.*(end).*$   # .* takes everything, backtracks to find "end"

懒量词

在量词后加 ? 使其变懒:尽量少匹配。'<.+?>' 匹配 '<a>' 和 '<b>' 而非整体。懒量词从起始位置尝试最小匹配,逐步扩展。并非总是更快——取决于输入和模式。

regex
# Add ? to make any quantifier lazy (match as LITTLE as possible)
*?    # lazy zero-or-more
+?    # lazy one-or-more
??    # lazy zero-or-one
{n,m}?  # lazy range

# Lazy matches shortest possible
"<.*?>"      # in 'a "b" c "d" e', matches "b" then "d"
<a.*?>       # matches <a> to first >

# Compare: HTML tag matching
<.+>         # greedy: <b>bold</b> matches as ONE
<.+?>        # lazy: matches <b> then </b> separately

# Find quoted strings (lazy)
"([^"]*)"    # alternative: chars that aren't a quote

# Lazy can be slower in some cases
# Prefer negated classes when possible:
"[^"]*"      # often faster than ".*?"

# Multiple lazy on same line
(.*?)(.*?)(.*?)   # all lazy, but each takes minimum
04

锚点与边界

起始锚点 ^

^ 匹配字符串开始位置(零宽)。默认不匹配行首,需 m 标志才能在每行开头匹配。在字符类开头 [^abc] 表示取反,与锚点含义不同。^ 与 $ 配合做整行/整串验证。

regex
# ^ matches position at start of string (or line with /m)
^Hello        # "Hello" only at the start
^\d+          # digits at the start
^https?://    # URL at start of string

# With multiline flag /m, ^ matches start of each line
^\s*$         # blank lines (with /m, matches each)

# Useful for validation (must start with)
^[A-Z]        # must start with uppercase letter
^[a-zA-Z]     # must start with a letter

# ^ inside [] is DIFFERENT (negation)
[^abc]        # any char that is NOT a, b, or c

# Common validation: entire string is digits
^\d+$         # only digits, nothing else

# Line prefix matching (multiline)
^\s*#.*       # comment lines starting with #

结束锚点 $

$ 匹配字符串末尾(零宽)。在多行模式(m 标志)下匹配每行末尾。注意 $ 在 JS 中可匹配最后的换行符,而 \z 始终匹配真正的末尾(Python/PCRE)。用 ^...$ 做整串验证。

regex
# $ matches position at end of string (or before final newline)
end$          # "end" only at the end
\.\w{2,4}$    # file extension at end
\d+$          # digits at the end

# With multiline flag /m, $ matches end of each line
\s*$          # trailing whitespace per line (with /m)

# Combine ^ and $ for full-string validation
^\d{4}$        # exactly 4 digits, nothing else
^[A-Z][a-z]+$  # Capitalized word, entire string
^yes|no$       # WARNING: this is (^yes) OR (no$)

# Fix the precedence issue:
^(?:yes|no)$   # entire string is "yes" or "no"

# Email-ish validation
^[\w.+-]+@[\w-]+\.[a-z]{2,}$

# Multiline: find empty lines
^$             # empty lines (with /m)

单词边界 \b

\b 匹配单词字符(\w)与非单词字符(\W)之间的零宽位置。\bword\b 匹配完整单词,不会匹配 'sword' 中的 'word'。在表单验证中常用 ^...$ 或 \b...\b 防止部分匹配。

regex
# \b matches a position between a word char and non-word char
\bword\b      # "word" but not "wording" or "sword"
\bcat         # "cat" at start of word: cat, cats, but not scat
cat\b         # "cat" at end of word: cat, scat, but not cats
\bcat\b       # "cat" as a whole word only

# Useful for finding whole words
\bthe\b       # "the" but not "theater" or "breathe"

# Find words of specific length
\b\w{4}\b      # all 4-letter words
\b\w{4,}\b    # all words with 4+ letters

# Boundary at start/end of identifier
\bUser\b      # whole word "User"
\bid\b         # whole word "id" (not "id" in "ride")

# Variable name as whole word
\bcount\b     # "count" but not "discount" or "counter"

# Boundary between digit and non-digit
\b\d+\b       # whole numbers only

非单词边界 \B

\B 是 \b 的取反:匹配两个都是单词字符或都是非单词字符的位置。\Bword 匹配 'sword' 中的 'word' 但不匹配独立的 'word'。常用于查找嵌在其他词中的子串。

regex
# \B is the opposite of \b - matches where there is NO word boundary
\Bword        # "word" NOT at start of word: "sword", "password"
word\B        # "word" NOT at end of word: "wording", "wordsword"
\Bword\B      # "word" in middle of other word chars

# Find 'cat' inside another word only
\Bcat\B       # matches in " concatenate " (middle 'cat')

# Useful for matching inside identifiers
\Ba\B         # 'a' surrounded by word chars

# Position NOT at word boundary
\B-\B         # hyphen between word chars (e.g., "well-known")

# Practical: hyphenated compound word
\w+\B-\B\w+   # NOT what you want for "well-known"

# \B at start of string with first char being non-word
\B.           # any char that's NOT at a word boundary start

# Test password: 'a' must be inside word (rare)
\Ba\w+\B      # 'a' not at word edges

字符串锚点 \A \Z \z

\A 和 \z 始终匹配字符串的真正开头和末尾(不受 m 标志影响)。\Z 在 Python/PCRE 中匹配末尾或末尾换行之前。JS 不支持 \A \Z \z,需用 ^ 和 $ 配合 m 标志。

regex
# These anchors are NOT in JavaScript natively (use ^ and $)
# Available in Python, Java, PCRE, Ruby, etc.

\A            # absolute start of string (not affected by /m)
\Z            # end of string, or before final newline (Python/PCRE)
\z            # absolute end of string (no newline tolerance)

# Python example
import re
re.search(r"\Ahello", "hello\nworld")  # Match at start
re.search(r"world\Z", "hello\nworld\n")  # Match (before final \n)
re.search(r"world\z", "hello\nworld\n")  # No match (newline at end)

# Difference between ^ and \A with re.MULTILINE
text = "line1\nline2"
re.findall(r"^\w+", text, re.M)  # ['line1', 'line2']
re.findall(r"\A\w+", text, re.M)  # ['line1'] only

# JavaScript: only ^ and $ available
# Use ^ with /m for line starts
# Use $ with /m for line ends

# Ruby always treats ^ and $ as line anchors
# Use \A and \z for string anchors in Ruby
05

分组与捕获

捕获组 ()

圆括号创建捕获组,记录匹配的子串以便后续引用。exec/match 返回的数组中,索引 0 是整体匹配,1..n 是各捕获组。捕获会影响性能,无需捕获时用 (?:...) 非捕获组。

regex
# Parentheses create a capturing group
(abc)        # matches "abc" and captures it
(\d+)        # matches digits and captures them
(a)(b)(c)    # three separate capture groups

# Group with quantifier
(ab)+        # "ab", "abab", "ababab" (whole group repeated)
(\d{2,4})-   # 2-4 digits followed by hyphen

# Capturing in replacement (JS)
"2024-01-15".replace(/(\d{4})-(\d{2})-(\d{2})/, "$3/$2/$1")
# Result: "15/01/2024"

# Capturing in Python
import re
m = re.search(r"(\w+)@(\w+)", "[email protected]")
m.group(1)  # "user"
m.group(2)  # "example"
m.groups()  # ("user", "example")

# Backreference in same regex
(\w+)\s+\1   # repeated word: "hello hello"

组编号

组的编号按左括号出现顺序从 1 开始。嵌套组也是按左括号顺序:(a(b)(c)) 中 1=abc, 2=b, 3=c。最多 99 组(PCRE),JS 中无明确上限但建议尽量少用。

regex
# Groups are numbered left-to-right by opening paren
(A)(B)(C)        # group 1=A, 2=B, 3=C
((A)(B))(C)      # group 1=AB, 2=A, 3=B, 4=C

# Nested groups: outer first, then inner
(\d{4})-(\d{2})-(\d{2})    # 1=year, 2=month, 3=day
((\d{1,3})\.){3}(\d{1,3})  # 1=last octet., 2=octet, 4=last octet

# Reference groups in replacement
# JavaScript: $1, $2, $3
"abc".replace(/(a)(b)(c)/, "$3$2$1")  # "cba"

# Python: \1, \2 in re.sub; m.group(1)
re.sub(r"(\w+)@(\w+)", r"\2.\1", "user@host")  # "host.user"

# Java: $1, $2 in replaceAll
"x".replaceAll("(x)", "$1$1")  # "xx"

# PHP: $1 or \1 in preg_replace
preg_replace('/(\w+)@(\w+)/', '$2.$1', 'user@host')

# Whole match: $& (JS), \g<0> (Python), $0 (Java/PHP)

非捕获组 (?:...)

(?:...) 分组但不捕获,避免占用组编号并提升性能。常用于应用量词到多个字符或限定交替范围。在不需要引用子匹配时,优先使用非捕获组。

regex
# (?:...) groups WITHOUT capturing (saves memory, cleaner groups)
(?:abc)+         # "abc" repeated, but no capture
(?:\d{1,3}\.){3}\d{1,3}  # IPv4, no extra captures

# Compare: capturing vs non-capturing
(\d{4})-(\d{2})    # 2 groups captured
(?:\d{4})-(?:\d{2}) # 0 groups captured (just match)

# Use non-capturing when you don't need the captured value
https?://(?:www\.)?(\w+)  # capture just the domain
(?:Mr|Mrs|Ms)\.\s+\w+     # title prefix, no capture needed

# Combine with alternation
(?:cat|dog|bird)s?   # plural animals, no capture

# With quantifier on alternation
(?:\d+\s*)+         # numbers separated by spaces

# Why use it?
# 1. Saves memory (no captured text stored)
# 2. Keeps group numbering clean
# 3. Documents intent: "this is just for grouping"

命名组 (?<name>...)

命名组让捕获更具可读性: (?<year>\d{4})。JS/Python/PCRE 用 (?<name>...),.NET 用 (?<name>...) 或 (?'name'),Java 7+ 也支持。引用时用 \k<name> 或 match.groups.name。

regex
# Named capture group - JavaScript, .NET, Python 3.x style
(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})

# Python uses (?P<name>...) - same as PCRE
(?P<year>\d{4})-(?P<month>\d{2})

# Reference in replacement
# JavaScript:
"2024-01-15".replace(
  /(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/,
  "$<d>/$<m>/$<y>"
)  # "15/01/2024"

# Python:
re.sub(
  r"(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})",
  r"\g<d>/\g<m>/\g<y>",
  "2024-01-15"
)  # "15/01/2024"

# Reference by name in same regex (backreference)
(?<word>\w+)\s+\k<word>     # JS
(?P<word>\w+)\s+(?P=word)    # Python

# Access named groups in Python
m = re.match(r"(?P<year>\d{4})", "2024")
m.group("year")  # "2024"
m.groupdict()    # {"year": "2024"}

交替 |

| 表示或:cat|dog 匹配 'cat' 或 'dog'。交替优先级最低,因此 ^a|b 匹配 'a 开头' 或 'b',要用 ^(a|b) 限定范围。引擎按顺序尝试各分支,首个匹配胜出(非最长匹配)。

regex
# | means OR - matches one of several alternatives
cat|dog|bird     # "cat" OR "dog" OR "bird"
yes|no           # "yes" or "no"
\d{2}|\d{4}     # 2 digits OR 4 digits

# IMPORTANT: alternation has low precedence
^abc|def$        # means (^abc) OR (def$) — NOT ^abc$ or ^def$

# Use groups to scope alternation
^(abc|def)$      # entire string is "abc" or "def"
^(?:cat|dog)s?$  # "cat", "cats", "dog", "dogs"

# Order matters - first match wins
a|ab             # in "ab", matches just "a" (leftmost wins)
ab|a             # in "ab", matches "ab"

# Common: file extensions
\.(?:jpg|png|gif|bmp)$

# HTML tags
</?(?:div|span|p|a)\b

# Day names
(?:Mon|Tues?|Wed|Thurs?|Fri|Sat|Sun)day

# Number formats
\d{1,3}(?:,\d{3})*|\d+   # 1,234 or 1234

组反向引用

\1 引用第 1 个捕获组匹配的内容,\k<name> 引用命名组。用于匹配重复内容,如 (\w+)\s+\1 匹配重复单词。反向引用会降低性能并禁用某些优化,RE2 不支持。

regex
# \1, \2, etc. refer to a previous capturing group's match
(\w+)\s+\1        # repeated word: "hello hello"
<(\w+)>.*?</\1>    # HTML tag matching: <div>...</div>
(["\'])[^\1]*\1   # quoted string (quote char remembered)

# Named backreferences
# JavaScript: \k<name>
(?<q>["\'])[^\k<q>]*\k<q>   # quoted string with named ref

# Python: (?P=name)
(?P<q>["\'])[^\2]*?(?P=q)    # similar (numbered ref still works)

# Practical: matching opening/closing tags
<b>(.*?)</\1>    # WARNING: \1 is "b", but...

# Better: capture tag name
<([a-z][a-z0-9]*)\b[^>]*>[\s\S]*?</\1>

# Find duplicated words
\b(\w+)\s+\1\b   # "the the", "is is"

# Match paired delimiters
(\[)[^\]]*\1\]    # WRONG: matches "[[..[" — use \1 for SAME char

# Date with same separator
(\d{4})([-/])(\d{2})\2(\d{2})  # 2024-01-15 (same separator)
06

非捕获组

基础非捕获

(?:...) 是最常用的非捕获组:仅分组不记录匹配。在不需要后续引用子串时,应优先使用而非普通捕获组。它能提升匹配速度并避免污染组编号。

regex
# (?:pattern) - group without capturing
(?:abc)         # matches "abc", no capture
(?:foo|bar)     # "foo" or "bar", no capture

# Compare
(\d{4})-(\d{2})-(\d{2})     # 3 capture groups
(?:\d{4})-(?:\d{2})-(?:\d{2})  # 0 capture groups (just match)

# When you only care about match/not-match
^(?:Yes|No|Maybe)$       # validate yes/no/maybe
https?://(?:www\.)?\w+  # URL prefix, only domain matters

# Keep capture numbers clean
(?:https?://)?(\w+)    # group 1 = domain (prefix is non-capturing)
vs
(https?://)?(\w+)      # group 1 = protocol, group 2 = domain

# Nested
(?:(?:foo|bar)-)?(\w+)  # group 1 = word (after optional prefix)

配合量词

(?:abc)+ 匹配 'abc' 一次或多次,而 abc+ 只匹配 'ab' 后跟多个 'c'。非捕获组让量词作用于整个子模式。使用 (?:...)? 实现可选整块内容,如 (?:\.\d+)? 匹配可选小数部分。

regex
# Apply quantifier to a non-capturing group
(?:abc)+          # "abc", "abcabc", "abcabcabc"
(?:\d{1,3}\.){3}\d{1,3}  # IPv4: 3 groups of "ddd." then "ddd"
(?:\s*,\s*)      # comma with optional surrounding whitespace
(?:\w+\s+)+\w+\.?  # word sequence (sentence-ish)

# Compare: capturing group + quantifier keeps LAST match only
(\d,)+           # in "1,2,3,", group 1 captures "3," (last)
(?:\d,)+         # same match, no capture

# Optional group with separator
\d{4}(?:-\d{2}){2}    # 2024-01-15 (no captures)
\d{4}(?:[-/]\d{2}){2} # 2024-01-15 or 2024/01/15

# Repeated pattern
(?:\d+\.)*\d+        # version number like 1.2.3

# Multiple non-capturing groups
^(?:Mr|Mrs|Ms|Dr)\.?\s+(?:[A-Z][a-z]+\s?)+$  # full name w/ title

# Avoid extra captures when extracting specific data
https?://(?:www\.)?([\w-]+)\.(?:com|org|net)  # only capture domain

嵌套分组

组可以嵌套:((?:a|b)(?:c|d))。外层捕获组记录整体,内层非捕获组仅分组。合理搭配可只捕获需要的部分,提升可读性和性能。注意每层都增加解析开销。

regex
# Mix capturing and non-capturing groups
(?:(\d{4})-(\d{2})-(\d{2}))  # outer non-cap, 3 inner captures
# group 1=year, 2=month, 3=day

# With alternation inside non-capturing
(?:https?|ftp)://([^/]+)  # group 1 = host

# Date with optional time
(\d{4}-\d{2}-\d{2})(?:[ T](\d{2}:\d{2}))?
# group 1 = date, group 2 = optional time

# Practical: parse URL
(?:([^:]+)://)?     # group 1: optional scheme
([^/?#]+)           # group 2: host
(?:/([^?#]*))?      # group 3: optional path
(?:\?([^#]*))?      # group 4: optional query
(?:#(.*))?          # group 5: optional fragment

# CSV-ish parser (simplified)
(?:^|,)(?:"([^"]*)"|([^,]*))
# group 1: quoted field, group 2: unquoted field

# Email-ish
(?:[\w.+-]+)@((?:[\w-]+\.)+[a-z]{2,})
# group 1 = domain (non-capturing group inside)

性能优势

非捕获组避免了捕获组的内存分配和子串保存开销。在大量匹配或长文本场景下,改用 (?:...) 可显著提升性能。RE2 等引擎根本不支持捕获组,所有分组均为非捕获。

regex
# Non-capturing groups are slightly faster
# They skip storing the matched text

# Microbenchmark concept (Python)
import re, time
text = "a" * 1000

# Capturing version
r1 = re.compile(r"(a)+")
# Non-capturing version
r2 = re.compile(r"(?:a)+")
# Both match same text, but r2 is marginally faster

# More importantly: avoids unnecessary group data
# Use non-capturing when you don't need group(N)

# In hot loops, this matters:
patterns = [
    r"(?:\s*,\s*)",        # non-capturing
    r"(\s*,\s*)",          # capturing (waste if unused)
]

# When extraction IS needed, use capturing:
re.findall(r"(\w+)@(\w+)", "a@b c@d")  # need the captures

# When only matching matters, use non-capturing:
re.search(r"(?:\d{1,3}\.){3}\d{1,3}", text)  # just match test

# Replace with no group reference
re.sub(r"(?:\s)+", "_", "a  b   c")  # "a_b_c" (no groups used)

常见用途

非捕获组常用于:限定交替范围 (?:foo|bar)、应用量词 (?:\d{1,3}\.){3}\d{1,3} 匹配 IP、组合可选块 (?:\.\w+)?。它是书写清晰正则的基础工具。

regex
# 1. Grouping for alternation
(?:cat|dog|bird)s?         # plural animal names
\.(?:jpg|jpeg|png|gif)$    # image extensions
^(?:Mr|Mrs|Ms|Dr)\.\s     # honorifics

# 2. Optional/required sub-patterns
https?://(?:www\.)?       # protocol + optional www
\d{4}(?:-?\d{2}){2}      # date with optional hyphens

# 3. Repeated complex sub-pattern
(?:\d{1,3}\.){3}\d{1,3}  # IPv4
(?:\s*\d+\s*)+,          # comma-separated numbers

# 4. Path-style patterns
(?:/[a-z][\w-]*)+         # URL path like /api/users/123
(?:\.\w+)+               # file.txt.bak (multiple extensions)

# 5. Quoted strings with escapes
"(?:[^"\\]|\\.)*"      # double-quoted with escapes

# 6. Header parsing
^(\w+):\s*(.*)$          # name: value (just capture both)

# 7. Programming keyword + identifier
(?:const|let|var)\s+(\w+)  # only capture the variable name

# 8. CSV with optional quotes
(?:^|,)(?:"([^"]*)"|([^,]*))  # capture quoted OR unquoted
07

预查与预查反向

正向预查 (?=)

(?=...) 要求当前位置后跟指定模式,但不消耗字符(零宽)。\d+(?=px) 匹配 '100px' 中的 '100' 但不包含 'px'。预查内的捕获组仍会记录,但 JS 的 matchAll 行为需注意。

regex
# (?=...) - matches if pattern follows, WITHOUT consuming
foo(?=bar)        # "foo" only if followed by "bar"
\d+(?=px)         # digits only if followed by "px"
\w+(?=@)          # word chars only if followed by "@"

# Lookahead doesn't consume - the next match starts after "foo"
"foobar".match(/foo(?=bar)/)  # match: "foo", index 0
# Next search continues at index 3, "bar" is still there

# Multiple lookaheads (all must match)
\d+(?=\s)(?=.*\bUSD\b)   # digit followed by space AND USD somewhere

# Password: must contain a digit
^(?=.*\d).{8,}$    # at least 8 chars, contains a digit

# Word boundary alternative using lookahead
\w+(?=\W|$)        # word chars until non-word or end

# Find numbers not followed by units
\d+(?!px)(?!em)    # number not followed by px or em

# Capture inside lookahead
(\w+)(?=(\W))     # word + capture the following non-word char

负向预查 (?!)

(?!...) 要求当前位置后不跟指定模式(零宽)。\d+(?!px) 匹配后面不是 'px' 的数字。注意它不要求后面有内容,'100' 后无字符也算 '不是 px'。常用于排除特定上下文。

regex
# (?!...) - matches if pattern does NOT follow
foo(?!bar)        # "foo" only if NOT followed by "bar"
\d+(?!px)         # digits NOT followed by "px"
\w+(?!@\w)        # word NOT followed by @word

# Q-w-o-T: find "q" not followed by "u"
q(?!u)            # matches "q" in "Iraq" but not "quiet"

# Negative lookahead for "not this word"
\b(?!and\b|or\b|not\b)\w+  # any word except "and", "or", "not"

# Number NOT followed by decimal
\d+(?!\.\d)      # "100" in "100 " but not in "100.5"

# Email local part: no consecutive dots
^(?!.*\.\.)[\w.]+@  # reject "a..b@..."

# Password: must NOT contain "password"
^(?!.*password).{8,}$   # 8+ chars, no "password" substring

# Disallow specific file extensions
\.\w+(?!\.(?:exe|bat|dll)$)  # not an executable extension

# Find "cat" not preceded by "scat" (would need lookbehind)
# but here: cat not FOLLOWED by "s" or "egory"
\bcat(?!s|egory)\b

正向预查反向 (?<=)

(?<=...) 要求当前位置前有指定模式(零宽)。(?<=\$)\d+ 匹配 '$100' 中的 '100'。JS(ES2018+)、Python、PCRE、Java 都支持,但 JS 早期版本和 Safari 旧版不支持。预查反向模式长度需固定(PCRE/JS 旧版)。

regex
# (?<=...) - matches if pattern PRECEDES (without consuming)
(?<=\$)\d+        # digits preceded by "$"
(?<=#)\w+          # word chars preceded by "#" (hex color)
(?<=Mr\.)\s\w+    # word after "Mr."

# Lookbehind doesn't consume - match starts AFTER the prefix
"$100".match(/(?<=\$)\d+/)  # match: "100", index 2

# Get value after specific label
(?<=total:\s*)\d+\.\d{2}   # number after "total:"

# Find text inside quotes
(?<=")[^"]+(?=")   # content between double quotes

# Currency amount
(?<=USD\s)\d+(?:\.\d{2})?  # number after "USD "

# Capture domain after @
(?<=@)[\w.]+      # domain part of email

# Path segment after /api/
(?<=/api/)[^/?#]+  # resource name in URL

# IMPORTANT: many engines require FIXED-LENGTH lookbehind
# (?<=ab|abc)  -- INVALID in some engines (variable length)
# (?<=ab|cd)   -- OK in most (same length alternatives)
# (?<=\w+)    -- INVALID (variable length) in old engines

# JavaScript (ES2018+), .NET, and newer PCRE allow variable length

负向预查反向 (?<!)

(?<!...) 要求当前位置前没有指定模式(零宽)。(?<!\$)\d+ 匹配前面不是 '$' 的数字。与负向预查一样,不要求前面有内容。常用于排除特定前缀,如 (?<!\w)word(?!\w) 匹配独立单词。

regex
# (?<!...) - matches if pattern does NOT precede
(?<!\$)\d+        # digits NOT preceded by "$"
(?<!\w)\w+        # word NOT preceded by word char (start of word)
(?<!Mr\.)\s\w+   # space+word NOT after "Mr."

# Find "cat" not preceded by "scat" or "concat"
(?<!s)cat\b       # "cat" not preceded by "s" (matches "cat" alone)

# Number not preceded by a sign
(?<![-+])\d+      # unsigned numbers

# Word not after specific prefix
(?<!un)\w+        # word not preceded by "un"

# Currency validation
(?<!\d)\d{1,3}(?:,\d{3})*(?:\.\d{2})?(?!\d)
# number with thousand separators, not part of larger number

# Prevent matching substring inside larger identifier
(?<![_\w])count\b(?!er)  # "count" not part of "counter"/"_count"

# Remove spaces NOT after periods
text.replace(/(?<!\.)  +/g, " ")
# collapse double spaces unless after a period

# Find "and" not preceded by "&" (avoid matching "&" alone)
(?<!&)\b\w+\b

验证模式

预查常用于在单次匹配中验证多个条件:^(?=.*[A-Z])(?=.*[a-z])(?=.*\d).{8,}$ 密码需同时含大小写字母和数字。每个预查独立从起始位置检查,互不消耗字符。

regex
# Lookarounds are perfect for "must contain" / "must not contain"

# Password: 8+ chars, 1+ uppercase, 1+ lowercase, 1+ digit
^(?=.*[A-Z])(?=.*[a-z])(?=.*\d).{8,}$

# Stronger: also require special char
^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$

# Must NOT contain "password" (case-insensitive)
^(?!.*password).{8,}$

# Email: must have @ and a dot after it
^(?=[^@]*@[^@]*\.[^@]*$).+$

# US phone: 10 digits possibly with separators
^(?=(?:\D*\d){10}\D*$)[\d\s()-]+$

# IPv4: 4 octets 0-255
^(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)$

# Hex color: 3 or 6 hex digits, optional #
^#?(?:[0-9a-fA-F]{3}){1,2}$

# Date: YYYY-MM-DD with basic validation
^(?!0000)\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$

# Username: 3-20 chars, alphanumeric + underscore, not starting with digit
^(?=[A-Za-z_])[A-Za-z0-9_]{3,20}$

预查限制

JS 旧版和 PCRE 要求预查反向的模式长度固定(不能用 *、+、{n,m})。ES2018+ 支持变长预查反向。预查会增加回溯开销,能用普通分组替代时优先用普通分组。Safari 旧版不支持预查反向。

regex
# 1. Lookbehind must be FIXED LENGTH in many engines
# Python (before 3.7), Java, older PCRE
(?<=abc|abcd)x    # INVALID - alternatives of different lengths
(?<=abc|def)x     # OK - same length

# Python 3.7+, JS (ES2018+), .NET, PCRE 4.5+ allow variable length
(?<=\w+)x        # OK in modern engines

# 2. Lookbehind CANNOT be unbounded
(?<=.*)x         # INVALID in most engines (no quantifier)

# 3. Capture groups inside lookaround
# DO capture, but the lookahead itself doesn't consume
(\w+)(?=(\W))   # group 1 = word, group 2 = next non-word

# 4. JavaScript lookbehind support (ES2018+)
# Old Safari versions don't support lookbehind
# Use capturing alternative for old browsers:
# Instead of (?<=\$)\d+, use \$(\d+) and use group 1

# 5. Lookarounds are zero-width
# They don't move the cursor
"abc".match(/(?=a)a/)   # match: "a"
"abc".match(/(?=a)b/)   # no match (can't be both a and b)

# 6. Nested lookarounds (legal but complex)
(?=(?:.*\d){3}).+   # must contain at least 3 digits

# 7. Performance: lookarounds can be slow with catastrophic patterns
(?=(a+)+)b           # avoid nested quantifiers in lookahead
08

替换与替换

基础替换

JS 的 replace 默认只替换第一个匹配,需 g 标志替换全部。Python 的 re.sub 默认替换全部,用 count 参数限制数量。替换字符串中的 $ 具有特殊含义,需注意转义。

regex
# Replace in different languages

# JavaScript
"hello world".replace(/world/, "there")     # "hello there"
"hello world".replace(/o/g, "0")            # "hell0 w0rld"

# Python
import re
re.sub(r"world", "there", "hello world")    # "hello there"
re.sub(r"o", "0", "hello world")            # "hell0 w0rld"

# Java
"hello world".replaceAll("o", "0")          # "hell0 w0rld"

# PHP
preg_replace('/o/', '0', 'hello world')     # "hell0 w0rld"

# Replace first occurrence only
"hello world".replace(/o/, "0")             # "hell0 world" (JS)
re.sub(r"o", "0", "hello world", count=1)   # "hell0 world" (Py)

# Whole-match reference: $& (JS), $0 (Java/PHP), \g<0> (Py)
"abc".replace(/\w/g, "$&!")                # "a!b!c!"

# Python: use a function
re.sub(r"\w", lambda m: m.group(0) + "!", "abc")  # "a!b!c!"

捕获组替换

$1 $2 ... 引用捕获组(JS/.NET),\1 \2 在 Python/PCRE 中使用。$& 引用整体匹配,\g<0> 是 Python 等价物。注意 JS 用 $,Python 用 \,二者不能混用。

regex
# Reference captured groups in replacement

# JavaScript: $1, $2, ... for groups; $& for whole match
"2024-01-15".replace(/(\d{4})-(\d{2})-(\d{2})/, "$3/$2/$1")
# Result: "15/01/2024"

# Swap two words
"hello world".replace(/(\w+) (\w+)/, "$2 $1")  # "world hello"

# Python: \1, \2 OR \g<1>, \g<2> (preferred for clarity)
re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\3/\2/\1", "2024-01-15")
# Result: "15/01/2024"

# Java: $1, $2
"2024-01-15".replaceAll("(\d{4})-(\d{2})-(\d{2})", "$3/$2/$1")

# PHP: $1 or \1
preg_replace('/(\d{4})-(\d{2})-(\d{2})/', '$3/$2/$1', '2024-01-15')

# .NET: ${name} for named groups
# "John".Replace(...) uses ${first} if pattern is (?<first>\w+)

# Use $& (JS) or $0 (Java) for entire match
"hello".replace(/\w/g, "[$&]")    # "[h][e][l][l][o]"

# Literal $ in replacement (JS) - use $$
"price".replace(/price/, "$$100")  # "$100"

命名组替换

JS 用 $<name>,Python 用 \g<name>,Java 用 ${name},.NET 用 ${name}。命名组让替换模板更易读,尤其在复杂模式中。命名组必须在模式中先定义,否则报错或无效。

regex
# Reference named capture groups in replacement

# JavaScript: $<name>
"2024-01-15".replace(
  /(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/,
  "$<d>/$<m>/$<y>"
)  # "15/01/2024"

# Python: \g<name>
re.sub(
  r"(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})",
  r"\g<d>/\g<m>/\g<y>",
  "2024-01-15"
)  # "15/01/2024"

# .NET: ${name}
Regex.Replace("2024-01-15",
  @"(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})",
  "${d}/${m}/${y}")

# PHP: \k<name> in pattern, but in replacement use \1 or $1
# (PHP doesn't directly support named backrefs in replacement)
preg_replace(
  '/(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})/',
  '$3/$2/$1',  # use numbered refs
  '2024-01-15'
)

# Java: ${name} (Java 7+)
"2024-01-15".replaceAll(
  "(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})",
  "${d}/${m}/${y}")

# Swap using named groups
"John Doe".replace(/(?<first>\w+) (?<last>\w+)/, "$<last>, $<first>")
# "Doe, John"

替换所有匹配

JS: str.replaceAll(regex, str) 需 g 标志,或 str.replace(/.../g, ...)。Python: re.sub(pattern, repl, str) 默认全部替换。性能: 简单字符串替换用 str.replaceAll(literal, ...) 比 regex 快。

regex
# Replace ALL matches - critical difference between languages

# JavaScript: needs /g flag for replaceAll behavior
"a-b-c".replace(/-/g, "+")           # "a+b+c"
"a-b-c".replace(/-/, "+")            # "a+b-c" (only first!)

# JavaScript: .replaceAll (ES2021+) requires /g flag
"a-b-c".replaceAll("-", "+")         # "a+b+c" (string pattern)
"a-b-c".replaceAll(/-/g, "+")        # "a+b+c" (regex needs /g)

# Python: re.sub replaces ALL by default
re.sub(r"-", "+", "a-b-c")           # "a+b+c"
# Use count parameter for limited replacement
re.sub(r"-", "+", "a-b-c", count=1)  # "a+b-c"
re.sub(r"-", "+", "a-b-c", count=2)  # "a+b+c" wait, just first 2

# Java: replaceAll takes a regex
"a-b-c".replaceAll("-", "+")         # "a+b+c"
# replaceFirst for just one
"a-b-c".replaceFirst("-", "+")       # "a+b-c"
# Note: String.replace (no "All") does literal replace of ALL
"a-b-c".replace("-", "+")            # "a+b+c" (literal, not regex)

# PHP: preg_replace replaces all by default
preg_replace('/-/', '+', 'a-b-c')    # "a+b+c"
# Use $limit parameter
preg_replace('/-/', '+', 'a-b-c', 1) # "a+b-c"

# Go (noreplaceAll in regexp package)
re := regexp.MustCompile("-")
re.ReplaceAllString("a-b-c", "+")    # "a+b+c"

函数替换

replace 的第二个参数可为函数: (match, p1, p2, ..., offset, string) => string。函数可动态生成替换内容,适合复杂转换(如模板、本地化)。Python 用 re.sub(pattern, fn, str),函数接收 match 对象。

regex
# Use a function for dynamic replacement (JS, Python)

# JavaScript: function receives (match, g1, g2, ..., offset, string)
"hello world".replace(/\w+/g, w => w.toUpperCase())
# "HELLO WORLD"

# Reverse each word
"hello world".replace(/\w+/g, w => w.split("").reverse().join(""))
# "olleh dlrow"

# Increment numbers
"a1 b2 c3".replace(/\d/g, d => parseInt(d) + 1)
# "a2 b3 c4"

# Python: function receives a match object
import re
re.sub(r"\w+", lambda m: m.group(0).upper(), "hello world")
# "HELLO WORLD"

# Use captured groups in Python function
def swap(m):
    return f"{m.group(2)} {m.group(1)}"
re.sub(r"(\w+) (\w+)", swap, "hello world")  # "world hello"

# Java: Matcher.appendReplacement with StringBuilder
Matcher m = Pattern.compile("\\d+").matcher("a1 b2");
StringBuilder sb = new StringBuilder();
while (m.find()) {
    m.appendReplacement(sb, String.valueOf(Integer.parseInt(m.group()) + 10));
}
m.appendTail(sb);  # "a11 b12"

# PHP: preg_replace_callback
preg_replace_callback('/\d+/', function($m) {
    return $m[0] + 10;
}, 'a1 b2');  # "a11 b12"

常见替换模式

常用模式: $& 插入整体匹配,$` 插入匹配前内容,$' 插入匹配后内容(JS)。$$ 转义为字面 $。Python 中 \g<0> 等价 $&。理解这些特殊变量能简化许多文本处理任务。

regex
# Trim leading/trailing whitespace
"  hi  ".replace(/^\s+|\s+$/g, "")        # "hi"

# Collapse multiple spaces into one
"a   b    c".replace(/\s+/g, " ")         # "a b c"

# Replace newlines with <br>
"text\nmore".replace(/\n/g, "<br>")      # "text<br>more"

# Strip HTML tags
"<p>Hello</p>".replace(/<[^>]+>/g, "")     # "Hello"

# Convert kebab-case to camelCase
"my-variable".replace(/-([a-z])/g, (_, c) => c.toUpperCase())
# "myVariable"

# CamelCase to snake_case
"myVar".replace(/([A-Z])/g, "_$1").toLowerCase()
# "my_var"

# Escape regex metacharacters
"1.5".replace(/[.*+?^${}()|[\]\\]/g, "\\$&")  # "1\\.5"

# Mask emails: hide local part
"user@host".replace(/(^.).+(@.+)/, "$1***$2")  # "u***@host"

# Format phone: 1234567890 -> 123-456-7890
"1234567890".replace(/(\d{3})(\d{3})(\d{4})/, "$1-$2-$3")

# URL-encode unsafe characters
"hello world".replace(/[^A-Za-z0-9]/g, c => "%" + c.charCodeAt(0).toString(16))
# "hello%20world"
09

标志

全局 g

g 标志让匹配从 lastIndex 继续而非每次从开头。test()/exec() 在 g 模式下有状态(lastIndex),重复调用会遍历所有匹配。match() 在 g 模式下返回所有匹配数组(无捕获组详情),用 matchAll() 获取详情。

regex
# g flag - match ALL occurrences, not just the first

# JavaScript
"abcabc".match(/a/g)       # ["a", "a"] (all matches)
"abcabc".match(/a/)        # ["a"] (first match only)
"a-b-c".replace(/-/g, "+") # "a+b+c"

# Without /g, replace() only does first match
"a-b-c".replace(/-/, "+")  # "a+b-c"

# With /g, .match returns array of all matches (or null)
"aaa".match(/a/g)          # ["a", "a", "a"]

# WARNING: /g with exec() maintains lastIndex state (gotcha)
const re = /a/g;
re.exec("aaa")  # index 0, lastIndex=1
re.exec("aaa")  # index 1, lastIndex=2
re.exec("aaa")  # index 2, lastIndex=3
re.exec("aaa")  # null, lastIndex=0 (resets)

# Use String.matchAll (ES2020+) for safer iteration
const matches = "aaa".matchAll(/a/g);
for (const m of matches) console.log(m.index);

# Python (re.sub finds all by default; re.findall)
re.findall(r"a", "aaa")    # ["a", "a", "a"]
re.finditer(r"a", "aaa")   # iterator of match objects

# Java: replaceAll = with /g; replaceFirst = without
# PHP: preg_match_all = with /g; preg_match = without
preg_match_all('/a/', 'aaa', $m)  # finds all

忽略大小写 i

i 标志使匹配忽略大小写: /hello/i 匹配 'Hello'、'HELLO'。在 Unicode 模式(u 标志)下,i 会启用 Unicode 大小写折叠(如 ß 匹配 SS)。Python 用 re.IGNORECASE,Java 用 Pattern.CASE_INSENSITIVE。

regex
# i flag - ignore case
/abc/i.test("ABC")      # true
/abc/i.test("AbC")      # true

# Match HTML tags case-insensitively
/<div>/i.test("<DIV>")   # true
text.replace(/<br\s*\/?>/gi, "\n")  # <br>, <BR>, <Br/>

# Case-insensitive word match
"Hello hello HELLO".match(/hello/gi)  # ["Hello", "hello", "HELLO"]

# Email is case-insensitive in practice
"[email protected]".match(/^[\w.+-]+@[\w.-]+\.[a-z]{2,}$/i)

# Python
re.search(r"abc", "ABC", re.IGNORECASE)  # match
re.IGNORECASE  # constant
re.I           # shorthand

# Java
"ABC".matches("(?i)abc")  # true (inline flag)
Pattern.compile("abc", Pattern.CASE_INSENSITIVE)

# Inline flags (most engines): (?i)
# (?i)abc  ==  /abc/i
# abc(?i)def  == abc plus case-insensitive def
# (?i:abc)  == case-insensitive abc (PCRE/Java, scoped)

# PHP: '/abc/i'
preg_match('/abc/i', 'ABC')  # 1 (match)

# Go (RE2): always use FlagI
re := regexp.MustCompile("(?i)abc")
re.MatchString("ABC")  # true

# .NET
Regex.Match("ABC", "abc", RegexOptions.IgnoreCase)

多行 m

m 标志让 ^ 和 $ 匹配每行的开头和末尾,而非仅整个字符串。注意 \A \Z \z 不受影响。m 标志不影响 . 的行为(需 s 标志)。Python 用 re.MULTILINE,Java 用 Pattern.MULTILINE。

regex
# m flag - ^ and $ match start/end of EACH LINE (not just string)

# Without /m: ^ and $ are string-anchored
"line1\nline2".match(/^\w+/)   # ["line1"] (only first line)
"line1\nline2".match(/^\w+/gm) # ["line1", "line2"] (each line)

# Find empty lines
"text\n\nmore".match(/^$/gm)  # [""] (the empty line)

# Comment lines in code
"^#.*" with /gm finds all lines starting with #

# Process each line: replace leading whitespace
"  a\n b\n  c".replace(/^\s+/gm, "")
# "a\nb\nc"

# JavaScript
"hello\nworld".replace(/^/gm, "> ")
# "> hello\n> world"

# Python (re.MULTILINE)
re.findall(r"^\w+", "line1\nline2", re.MULTILINE)
# ["line1", "line2"]
re.M  # shorthand

# Java: Pattern.MULTILINE
Pattern p = Pattern.compile("^\w+", Pattern.MULTILINE)

# Inline: (?m)
# (?m)^\w+  ==  with /m flag

# PHP: '/^\w+/m'
# Go: (?m) inline
# .NET: RegexOptions.Multiline

# IMPORTANT: m does NOT change . (use s flag for that)
# m affects ^ and $; s affects .

dotall s

s 标志(ES2018+)让 . 匹配包括换行符在内的所有字符。无 s 时,. 不匹配 \n \r \u2028 \u2029。Python 用 re.DOTALL,Java 用 Pattern.DOTALL。旧版 JS 用 [\s\S] 替代 . 的 dotall 行为。

regex
# s flag - dot . matches ALL chars INCLUDING newline

# Without /s: . doesn't match newline
"a\nb".match(/a.b/)    # null
"a\nb".match(/a.b/s)   # ["a\nb"] (with /s)

# Match anything between tags (even across lines)
"<div>a\nb</div>".match(/<div>(.*?)<\/div>/s)
# captures "a\nb"

# JavaScript (ES2018+) - s flag
const re = /<div>[\s\S]*?<\/div>/  # traditional
const re2 = /<div>.*?<\/div>/s        # with /s

# Python: re.DOTALL or re.S
re.search(r"<div>.*?</div>", text, re.DOTALL)

# Inline: (?s)
# (?s).  matches any char including newline

# Java: Pattern.DOTALL
Pattern.compile(".*?", Pattern.DOTALL)

# PHP: '/.*/s'
# Go: (?s) inline
# .NET: RegexOptions.Singleline

# Naming is confusing:
# - JavaScript "s" = "dotAll" (dot matches all)
# - .NET "Singleline" = same (dot matches all)
# - Multiline (m) and Singleline (s) are INDEPENDENT
# - You can use BOTH /ms simultaneously

# Combine m and s
"line1\nline2".replace(/^.*/gms, "X")
# replaces each line entirely with "X"

# Portable alternative (works everywhere): [\s\S] or [\d\D]
/.?[\s\S]*?</tag>/  # portable "any char"

Unicode u

u 标志启用 Unicode 模式: 正确处理代理对,\u{1F600} 匹配 emoji,. 匹配完整码点而非 UTF-16 单元。同时启用 Unicode 属性转义 \p{...}。处理非 ASCII 文本时务必加 u 标志。

regex
# u flag - treat pattern as Unicode code points (not UTF-16 code units)

# JavaScript (ES6+)
# Without /u: matches UTF-16 code units (broken surrogate pairs)
"𝌆".length             # 2 (surrogate pair)
/^.$/.test("𝌆")         # false (two code units)
/^.$/u.test("𝌆")        # true  (one code point)

# Unicode property escapes (require /u)
/^\p{L}+$/u.test("Hello")   # true (letters)
/\p{Script=Han}/u.test("中文")  # true (CJK characters)
/\p{Emoji}/u.test("👋")       # true

# Case-insensitive Unicode folding
/\w/iu.test("Ω")             # true (Σ/σ/ς equivalence with /iu)

# Python 3: Unicode by default (use re.ASCII for ASCII-only)
re.search(r"\w+", "日本語")  # matches (Unicode default)
re.search(r"\w+", "日本語", re.ASCII)  # ASCII-only

# Python Unicode properties (3.13+ or regex module)
# \p{L} requires regex module or Python 3.13+
import regex  # third-party module
regex.search(r"\p{L}+", "Hello")

# Java: Pattern.UNICODE_CHARACTER_CLASS
Pattern.compile("\\w+", Pattern.UNICODE_CHARACTER_CLASS)
# Java's \w is Unicode by default; flag enables POSIX Unicode classes

# PHP: /u flag for UTF-8 mode
preg_match('/^\p{L}+$/u', 'Hello')  # 1 (Unicode)

# .NET: \p{L} works by default
# Go (RE2): \p{L} works by default

# IMPORTANT: with /u in JS, quantifiers apply to code points
/[😂]{2}/u.test("😂😂")  # true

粘附 y 与扩展 x

y 标志从 lastIndex 精确匹配(不跳过字符),用于词法分析。x 标志(PCRE/Python 的 re.VERBOSE)允许模式中含空白和注释,提升可读性。JS 不原生支持 x,但有 XRegExp 等库。

regex
# y flag (sticky) - JS only - matches at lastIndex exactly
const re = /abc/y;
re.lastIndex = 0;
re.test("abcabc")  # true, lastIndex now 3
re.test("abcabc")  # true, lastIndex now 6 (matches at exactly 3)
re.test("abcabc")  # false (would need to start at 6, no chars left)

# Useful for tokenizers (must match in sequence)
function tokenize(str) {
  const tokens = [];
  const ws = /\s*/y;
  const num = /\d+/y;
  const op = /[+\-*/]/y;
  let pos = 0;
  while (pos < str.length) {
    ws.lastIndex = pos; let m = ws.exec(str);
    if (m) pos = ws.lastIndex;
    if (pos >= str.length) break;
    num.lastIndex = pos; m = num.exec(str);
    if (m) { tokens.push(["num", m[0]]); pos = num.lastIndex; continue; }
    op.lastIndex = pos; m = op.exec(str);
    if (m) { tokens.push(["op", m[0]]); pos = op.lastIndex; continue; }
    throw new Error("Unexpected at " + pos);
  }
  return tokens;
}

# x flag (extended) - whitespace in pattern ignored, # starts comment
# JavaScript: ES2025+ proposal; widely supported in other engines

# Python (re.VERBOSE)
pattern = r"""
  \d{4}      # year
  -           # separator
  \d{2}      # month
  -           # separator
  \d{2}      # day
"""
re.match(pattern, "2024-01-15", re.VERBOSE)

# PHP: /x flag
preg_match('/
  \d{4}    # year
  -
  \d{2}    # month
/x', '2024-01')

# Inline: (?x)
# (?x)\d{4}-\d{2}  # comment allowed

# To match literal whitespace in /x mode, escape: \  or use [ ]
# \ \t\n all still work; just unescaped space/tab are ignored
10

常见模式

邮箱

邮箱正则复杂度依需求而定。简单版 ^[^@\s]+@[^@\s]+\.[^@\s]+$ 够用于基本校验。RFC 5322 完整正则极长,实际开发建议用 HTML5 type=email 或专用库,正则仅做粗筛。

regex
# Simplified email (good for most uses)
^[\w.+-]+@[\w.-]+\.[a-zA-Z]{2,}$

# More strict
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$

# RFC 5322 (simplified - the actual RFC is much more complex)
^(?:[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+
   (?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*)
  @
  (?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+
  [a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$

# JavaScript
const emailRe = /^[\w.+-]+@[\w.-]+\.[a-zA-Z]{2,}$/;
emailRe.test("[email protected]")      # true
emailRe.test("[email protected]")  # true

# Python
import re
re.match(r"^[\w.+-]+@[\w.-]+\.[a-z]{2,}$", "[email protected]", re.I)

# Practical note: the only true validation is sending an email.
# Reject clearly invalid formats, but accept edge cases.

# Common TLD length: 2-24 chars (newer TLDs can be long)
^[\w.+-]+@[\w.-]+\.[a-zA-Z]{2,24}$

URL

URL 正则需考虑协议(http/https)、域名、端口、路径、查询参数、锚点。简单版 https?://[\w.-]+ 匹配基本 URL。完整解析应使用 URL API(JS)或 urllib.parse(Python),正则易遗漏边界情况。

regex
# Basic URL pattern
^https?://[\w.-]+(?:\.[a-z]{2,})?(:\d+)?(?:/\S*)?$

# More complete URL parser
^(?:(\w+)://)?              # 1: scheme
([^/:?#]+)                  # 2: host
(?::(\d+))?                 # 3: port
([^?#]*)                    # 4: path
(?:\?([^#]*))?              # 5: query
(?:#(.*))?                  # 6: fragment
$

# JavaScript URL parsing
const urlRe = /^(https?):\/\/([^/:?#]+)(?::(\d+))?
              ([^?#]*)(?:\?([^#]*))?(?:#(.*))?$/ix;
const m = "https://example.com:8080/path?q=1#top".match(urlRe);
// m[1]="https", m[2]="example.com", m[3]="8080",
// m[4]="/path", m[5]="q=1", m[6]="top"

# Python
import re
url_re = re.compile(r'''
  ^(?P<scheme>https?)://
  (?P<host>[^/:?#]+)
  (?::(?P<port>\d+))?
  (?P<path>[^?#]*)
  (?:\?(?P<query>[^#]*))?
  (?:\#(?P<frag>.*))?
  $''', re.VERBOSE | re.IGNORECASE)

# Match domain only
^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$

# Validate URL using built-in URL class (JS)
try { new URL(input); } catch { /* invalid */ }

电话号码

电话号码格式因地区差异大。北美格式 ^\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$。国际格式用 E.164: ^\+[1-9]\d{1,14}$。生产环境建议用 libphonenumber 等专用库做验证和格式化。

regex
# US phone: 123-456-7890, (123) 456-7890, 1234567890
^(?:\+1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$

# Capture components
^(?:\+1[-.\s]?)?\(?\(?(\d{3})\)?[-.\s]?(\d{3})[-.\s]?(\d{4})$

# International (E.164): +[country][number], max 15 digits
^\+[1-9]\d{1,14}$

# China phone: 11 digits starting with 1
^1[3-9]\d{9}$

# UK phone: 0XXXXXXXXXX or +44XXXXXXXXXX
^(?:0|\+44)\d{10}$

# Generic international (loose)
^\+?[\d\s().-]{7,}$

# JavaScript
const phoneRe = /^(?:\+1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/;
phoneRe.test("(123) 456-7890")  # true
phoneRe.test("123-456-7890")    # true
phoneRe.test("1234567890")      # true

# Python: extract phone from text
re.findall(r"\b\d{3}[-.]?\d{3}[-.]?\d{4}\b", text)

# Format phone: 1234567890 -> (123) 456-7890
"1234567890".replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3")

IP 地址

IPv4: ^(25[0-5]|2[0-4]\d|[01]?\d?\d)(\.(25[0-5]|2[0-4]\d|[01]?\d?\d)){3}$ 严格校验 0-255。IPv6 正则更复杂,建议用 inet_pton(Python)或专用库。简单 \d{1,3}(\.\d{1,3}){3} 会匹配 999.999.999.999。

regex
# IPv4: four octets 0-255
^(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}
 (?:25[0-5]|2[0-4]\d|1?\d?\d)$

# Explanation:
# 25[0-5]      -> 250-255
# 2[0-4]\d     -> 200-249
# 1?\d?\d     -> 0-199 (1? optional leading 1, \d? optional tens)

# Python
import re
ipv4_re = re.compile(
    r'^(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)$'
)
ipv4_re.match("192.168.1.1")   # match
ipv4_re.match("256.0.0.1")     # None (256 > 255)

# Extract IPv4 from text
re.findall(r'\b(?:\d{1,3}\.){3}\d{1,3}\b', text)

# IPv6 (simplified)
^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$
# (:: shorthand, mixed notation are more complex)

# JavaScript
const ipv4 = /^(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)$/;
ipv4.test("0.0.0.0")       # true
ipv4.test("255.255.255.255") # true
ipv4.test("256.0.0.0")     # false

# Convert IP to number
"192.168.1.1".split(".").reduce((acc, o) => (acc << 8) + +o, 0) >>> 0

# Private IP ranges
^(?:10\.|172\.(?:1[6-9]|2\d|3[01])\.|192\.168\.)

十六进制颜色

^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ 匹配 3 位或 6 位十六进制颜色。扩展版支持 8 位(含 alpha): [0-9a-fA-F]{3,8}。注意 CSS4 还支持 rgb()/hsl() 函数语法,需另行处理。

regex
# Hex color: #RGB or #RRGGBB
^#?(?:[0-9a-fA-F]{3}){1,2}$

# Strict with #
^#(?:[0-9a-fA-F]{3}){1,2}$

# With optional alpha: #RGBA or #RRGGBBAA
^#(?:[0-9a-fA-F]{3,4}){1,2}$

# Capture RGB components
^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$

# JavaScript
const hexRe = /^#(?:[0-9a-fA-F]{3}){1,2}$/;
hexRe.test("#fff")       # true
hexRe.test("#FFAABB")    # true
hexRe.test("#12AB56")    # true
hexRe.test("#GGG")       # false
hexRe.test("fff")        # false (no #)

# Allow without #
const hexLoose = /^#?[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?$/

# Extract hex colors from CSS
const css = "color: #fff; background: #123456;";
const colors = css.match(/#[0-9a-fA-F]{3,6}/g);
// ["#fff", "#123456"]

# Convert #RGB to #RRGGBB
"#abc".replace(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/i, "#$1$1$2$2$3$3")
// "#aabbcc"

# Python
import re
re.match(r"^#(?:[0-9a-fA-F]{3}){1,2}$", "#abc123")

邮政编码

美国 ZIP: ^\d{5}(-\d{4})?$。中国邮编: ^\d{6}$。英国邮编格式复杂,正则较长。加拿大邮编: ^[A-Z]\d[A-Z] \d[A-Z]\d$。各国邮编规则差异大,需按目标地区定制。

regex
# US ZIP: 12345 or 12345-6789
^\d{5}(?:-\d{4})?$

# Canada postal: A1A 1A1 (letter digit letter space digit letter digit)
^[A-Za-z]\d[A-Za-z][ -]?\d[A-Za-z]\d$

# UK postcode (simplified)
^[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}$

# China postal code: 6 digits
^\d{6}$

# Germany: 5 digits
^\d{5}$

# France: 5 digits
^\d{5}$

# Australia: 4 digits
^\d{4}$

# JavaScript
const zipRe = /^\d{5}(?:-\d{4})?$/;
zipRe.test("12345")        # true
zipRe.test("12345-6789")   # true
zipRe.test("1234")         # false

# Canada postal
const caRe = /^[A-Za-z]\d[A-Za-z][ -]?\d[A-Za-z]\d$/;
caRe.test("M5V 2H1")       # true
caRe.test("m5v2h1")        # true

# Python
import re
re.match(r"^\d{5}(?:-\d{4})?$", "12345-6789")  # match

# Format ZIP: 123456789 -> 12345-6789
"123456789".replace(/(\d{5})(\d{4})/, "$1-$2")
11

密码验证

最小长度

^.{8,}$ 要求至少 8 个字符。注意 . 默认不匹配换行,密码一般不含换行可接受。结合 ^ 和 $ 确保整串满足条件。最小长度是密码策略的基础,建议至少 8 位,敏感场景 12+ 位。

regex
# At least 8 characters
^.{8,}$

# At least 12 characters (modern recommendation)
^.{12,}$

# Between 8 and 128 characters
^.{8,128}$

# Using lookahead (allows combining with other rules)
^(?=.{8,}$).*$

# JavaScript
const lenRe = /^.{8,}$/;
lenRe.test("short")        # false
lenRe.test("longenough")   # true

# Python
import re
bool(re.match(r"^.{8,}$", "longenough"))  # True

# Allow any character including newlines (use s flag or [\s\S])
# /^.{8,}$/s  matches multi-line password

# Common: 8+ with at least one of each type (lookaheads)
^(?=.{8,}$)             # at least 8 chars
(?=.*[a-z])             # at least one lowercase
(?=.*[A-Z])             # at least one uppercase
(?=.*\d)               # at least one digit
(?=.*[^A-Za-z0-9])      # at least one special char
.*$

大写字母要求

(?=.*[A-Z]) 正向预查确保含至少一个大写字母。预查不消耗字符,可串联多个条件。注意 i 标志会使 [A-Z] 等同 [A-Za-z],密码验证不应使用 i 标志。Unicode 大写需用 \p{Lu} 配合 u 标志。

regex
# Must contain at least one uppercase letter
^(?=.*[A-Z]).*$

# Must contain at least one uppercase, 8+ chars
^(?=.*[A-Z]).{8,}$

# At least two uppercase letters
^(?=(?:.*[A-Z]){2}).*$

# JavaScript
const upperRe = /^(?=.*[A-Z]).{8,}$/;
upperRe.test("password")        # false (no uppercase)
upperRe.test("Password")        # true
upperRe.test("PASSWORD")        # true

# Python
import re
bool(re.match(r"^(?=.*[A-Z]).{8,}$", "Password1"))  # True

# Count uppercase letters
const uppers = (pwd) => (pwd.match(/[A-Z]/g) || []).length;
uppers("PassWord")  # 2

# Allow Unicode uppercase (with /u flag in JS)
const unicodeUpper = /^(?=.*\p{Lu}).+$/u;
unicodeUpper.test("Ωμέγα")  # true (Ω is uppercase Greek)

# Python Unicode uppercase
re.search(r"\p{Lu}", "Ωμέγα", regex.UNICODE)  # needs 'regex' module

# .NET: \p{Lu} for any uppercase letter
# Java: \p{Lu} works with Pattern.UNICODE_CHARACTER_CLASS

# Without lookahead: just count
if (/[A-Z]/.test(password)) { /* has uppercase */ }

数字要求

(?=.*\d) 确保含至少一个数字。\d 在 ASCII 模式下等价 [0-9],Unicode 模式下可能匹配其他数字字符(如 ①)。要严格匹配 ASCII 数字,用 [0-9] 而非 \d。

regex
# Must contain at least one digit
^(?=.*\d).*$

# At least one digit, 8+ chars
^(?=.*\d).{8,}$

# At least three digits
^(?=(?:.*\d){3}).*$

# JavaScript
const digitRe = /^(?=.*\d).{8,}$/;
digitRe.test("password")        # false (no digit)
digitRe.test("password1")       # true
digitRe.test("pass1word2")      # true

# Count digits
const digits = (pwd) => (pwd.match(/\d/g) || []).length;
digits("pass12word")  # 2

# Python
import re
bool(re.match(r"^(?=.*\d).{8,}$", "password1"))  # True

# Unicode digits (e.g., Arabic-Indic digits)
/^.*\p{N}/u.test("password١")  # true (١ is Arabic-Indic 1)

# ASCII-only digits explicitly
^(?=.*[0-9]).*$

# Common password rule: must contain letter AND digit
^(?=.*[A-Za-z])(?=.*\d).{8,}$

# Alphanumeric only (no special chars)
^[A-Za-z0-9]{8,}$

# Without regex
const hasDigit = (s) => /\d/.test(s);
const hasLetter = (s) => /[A-Za-z]/.test(s);
if (hasDigit(pwd) && hasLetter(pwd) && pwd.length >= 8) { /* OK */ }

特殊字符

(?=.*[!@#$%^&*]) 确保含至少一个指定特殊字符。字符集可根据业务需求调整。注意在字符类 [] 内,大多数特殊字符无需转义,但 ] \ ^ - 需注意位置或转义。

regex
# Must contain at least one special (non-alphanumeric) char
^(?=.*[^A-Za-z0-9]).*$

# Or explicit list of special chars
^(?=.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>/?]).*$

# At least one special, 8+ chars
^(?=.*[^A-Za-z0-9]).{8,}$

# JavaScript
const specialRe = /^(?=.*[^A-Za-z0-9]).{8,}$/;
specialRe.test("password")     # false
specialRe.test("password!")    # true
specialRe.test("pass word")    # true (space is non-alphanumeric!)

# To exclude space from "special":
^(?=.*[!@#$%^&*\-_=+]).*$

# Python
import re
bool(re.match(r"^(?=.*[^A-Za-z0-9]).{8,}$", "Password1!"))  # True

# Count special characters
const specials = (pwd) => (pwd.match(/[^A-Za-z0-9]/g) || []).length;

# Common allow-list for special chars in passwords
const ALLOWED_SPECIAL = /[!@#$%^&*\-_=+?]/;
const hasAllowedSpecial = (pwd) => ALLOWED_SPECIAL.test(pwd);

# Disallow whitespace entirely
^(?=\S*$).{8,}$                # no whitespace, 8+ chars
^(?!.*\s).{8,}$                # equivalent

# Without regex
const hasSpecial = (s) => /[^A-Za-z0-9]/.test(s);
const noWhitespace = (s) => !/\s/.test(s);

组合验证

^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$ 组合多种要求。每个预查独立从开头检查,互不影响。这种写法清晰易维护,比单一复杂正则更易调整。注意预查顺序不影响结果。

regex
# Combined password rule:
# - 8+ chars
# - 1+ lowercase
# - 1+ uppercase
# - 1+ digit
# - 1+ special char
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$

# Stronger: 12+ chars, 2+ digits
^(?=.*[a-z])(?=.*[A-Z])(?=(?:.*\d){2})(?=.*[^A-Za-z0-9]).{12,}$

# JavaScript
const strongPwd = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/;
strongPwd.test("Password1!")   # true
strongPwd.test("password")     # false (no upper, digit, special)
strongPwd.test("PASSWORD1!")   # false (no lower)

# Python
import re
STRONG = re.compile(
    r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$"
)
bool(STRONG.match("Password1!"))  # True

# Readable alternative: separate checks (often clearer)
function validatePassword(pwd) {
  const checks = {
    length: pwd.length >= 8,
    lower: /[a-z]/.test(pwd),
    upper: /[A-Z]/.test(pwd),
    digit: /\d/.test(pwd),
    special: /[^A-Za-z0-9]/.test(pwd),
    noWhitespace: !/\s/.test(pwd),
  };
  return Object.values(checks).every(Boolean) ? checks : null;
}

# Disallow common weak passwords
^(?!password|123456|qwerty|letmein).{8,}$

# NIST guidance: focus on length, allow all char types,
# check against breach databases (haveibeenpwned API)
12

日期匹配

YYYY-MM-DD

^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$ 校验 YYYY-MM-DD 格式。月份限制 01-12,日期限制 01-31。此正则不校验闰年和大月小月,精确校验需结合 Date 对象或专用库。

regex
# Basic: 4 digits, dash, 2 digits, dash, 2 digits
^\d{4}-\d{2}-\d{2}$

# With basic validation (month 01-12, day 01-31)
^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$

# Disallow year 0000
^(?!0000)\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$

# JavaScript
const dateRe = /^(?!0000)\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$/;
dateRe.test("2024-01-15")   # true
dateRe.test("2024-13-01")   # false (month 13)
dateRe.test("2024-02-31")   # true (regex doesn't know Feb has 28 days)

# Capture components
^(\d{4})-(\d{2})-(\d{2})$
"2024-01-15".match(/^(\d{4})-(\d{2})-(\d{2})$/)
// ["2024-01-15", "2024", "01", "15"]

# Python
import re
m = re.match(r"^(\d{4})-(\d{2})-(\d{2})$", "2024-01-15")
year, month, day = m.groups()

# Use Date for real validation
const d = new Date("2024-01-15");
if (isNaN(d.getTime())) { /* invalid */ }

# Python: datetime for real validation
from datetime import date
try: date(2024, 1, 15)
except ValueError: pass  # invalid

DD/MM/YYYY

^(0[1-9]|[12]\d|3[01])/(0[1-9]|1[0-2])/\d{4}$ 匹配 DD/MM/YYYY(欧洲常见格式)。注意区分 DD/MM 与 MM/DD: 美国用 MM/DD/YYYY,需根据目标地区选择。生产环境建议用 Date.parse 或 moment/dayjs 解析。

regex
# DD/MM/YYYY with basic validation
^(?:0[1-9]|[12]\d|3[01])/(?:0[1-9]|1[0-2])/\d{4}$

# Capture day, month, year
^(\d{2})/(\d{2})/(\d{4})$

# With separators: / or - or .
^(\d{1,2})[/-](\d{1,2})[/-](\d{4})$

# JavaScript
const re = /^(\d{1,2})[/-](\d{1,2})[/-](\d{4})$/;
const m = "15/01/2024".match(re);
// m[1]="15", m[2]="01", m[3]="2024"

# US format MM/DD/YYYY
^(?:0[1-9]|1[0-2])/(?:0[1-9]|[12]\d|3[01])/\d{4}$

# Allow 1 or 2 digit day/month
^(\d{1,2})/(\d{1,2})/(\d{4})$

# Capture and reorder: DD/MM/YYYY -> YYYY-MM-DD
"15/01/2024".replace(/^(\d{2})\/(\d{2})\/(\d{4})$/, "$3-$2-$1")
// "2024-01-15"

# Python
import re
re.sub(r"^(\d{2})/(\d{2})/(\d{4})$", r"\3-\2-\1", "15/01/2024")
# "2024-01-15"

# Detect format ambiguity: 01/02/2024 - is it Jan 2 or Feb 1?
# Use ISO 8601 (YYYY-MM-DD) for unambiguous storage

时间格式

^([01]\d|2[0-3]):([0-5]\d)(:([0-5]\d))?$ 匹配 24 小时制 HH:MM 或 HH:MM:SS。12 小时制需额外处理 AM/PM: ^(0?[1-9]|1[0-2]):[0-5]\d ?[AP]M$。时区考虑更复杂,建议用专用时间库。

regex
# 24-hour: HH:MM
^([01]\d|2[0-3]):[0-5]\d$

# 24-hour: HH:MM:SS
^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$

# 12-hour with AM/PM
^(0?[1-9]|1[0-2]):[0-5]\d(?:\s?[AP]M)?$

# Capture HH, MM, SS
^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$

# With milliseconds
^([01]\d|2[0-3]):[0-5]\d:[0-5]\d\.\d{3}$

# JavaScript
const time24 = /^([01]\d|2[0-3]):[0-5]\d$/;
time24.test("23:59")  # true
time24.test("24:00")  # false
time24.test("12:30")  # true

const time12 = /^(0?[1-9]|1[0-2]):[0-5]\d\s?[AP]M$/i;
time12.test("1:30 PM")  # true
time12.test("12:00am")  # true

# Python
import re
re.match(r"^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$", "12:30:45")

# ISO 8601 duration: PT1H30M
^PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$

# Time zone offset: +HH:MM or -HH:MM
[+-]([01]\d|2[0-3]):?[0-5]\d

# Combined date and time (ISO 8601)
^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:Z|[+-]\d{2}:?\d{2})?$

ISO 8601 日期时间

^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})?$ 匹配 ISO 8601 格式。ISO 8601 是国际标准,JS 的 Date.parse 和 new Date() 原生支持。注意时区部分 Z 表示 UTC,+08:00 表示东八区。

regex
# ISO 8601 date
^\d{4}-\d{2}-\d{2}$

# ISO 8601 date and time (basic)
^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:Z|[+-]\d{2}:\d{2})?$

# With optional milliseconds
^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})?$

# JavaScript - use built-in Date parsing
const d = new Date("2024-01-15T10:30:00Z");
if (!isNaN(d.getTime())) { /* valid ISO */ }

# Validate ISO 8601 strictly
const isoRe = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?(?:Z|[+-]\d{2}:\d{2})$/;
isoRe.test("2024-01-15T10:30:00Z")           # true
isoRe.test("2024-01-15T10:30:00.123Z")       # true
isoRe.test("2024-01-15T10:30:00+08:00")      # true
isoRe.test("2024-01-15 10:30:00")            # false (T required)

# Python - use datetime.fromisoformat (3.7+)
from datetime import datetime
try:
    datetime.fromisoformat("2024-01-15T10:30:00")
except ValueError:
    pass  # invalid

# Capture date and time components
^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(Z|[+-]\d{2}:\d{2})?$

# Week date: 2024-W03-1 (year-week-weekday)
^\d{4}-W\d{2}-\d$

# Ordinal date: 2024-015 (year-day of year)
^\d{4}-\d{3}$

捕获各部分

使用命名组分别捕获年月日: (?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})。match.groups.year 直接获取年份。捕获后可进一步用 Number() 转换并做范围校验(如闰年判断),比纯正则更可靠。

regex
# Capture year, month, day from YYYY-MM-DD
^(\d{4})-(\d{2})-(\d{2})$
# Groups: 1=year, 2=month, 3=day

# JavaScript
const m = "2024-01-15".match(/^(\d{4})-(\d{2})-(\d{2})$/);
const [_, year, month, day] = m;
// year="2024", month="01", day="15"

# Named groups (clearer)
const m2 = "2024-01-15".match(
  /^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$)/
);
m2.groups.year  // "2024"
m2.groups.month // "01"
m2.groups.day   // "15"

# Python with named groups
import re
m = re.match(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})",
             "2024-01-15")
m.group("year")   # "2024"
m.groupdict()     # {"year": "2024", "month": "01", "day": "15"}

# Reformat date: YYYY-MM-DD -> DD/MM/YYYY
"2024-01-15".replace(
  /^(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})$/,
  "$<d>/$<m>/$<y>"
)
// "15/01/2024"

# Python
re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\3/\2/\1", "2024-01-15")
# "15/01/2024"

# Validate and extract date from text
const text = "Meeting on 2024-01-15 at 10:30";
const [_, y, mo, d] = text.match(/(\d{4})-(\d{2})-(\d{2})/);
// y="2024", mo="01", d="15"

# Convert to Date object
const d = new Date(+y, +mo - 1, +d);  # months are 0-indexed
13

数字匹配

整数

^-?\d+$ 匹配可选负号的整数。^-?\d+$ 包含 -0,若要排除用 ^(0|-?[1-9]\d*)$。\d+ 不匹配千分位逗号,如需支持 1,234 用 ^-?\d{1,3}(,\d{3})*$。注意 \d 在 Unicode 模式下可能匹配全角数字。

regex
# Positive integers
^\d+$

# Optional sign
^[+-]?\d+$

# Negative only
^-\d+$

# With thousand separators (US/EU: comma)
^[+-]?\d{1,3}(?:,\d{3})*$

# JavaScript
const intRe = /^[+-]?\d+$/;
intRe.test("12345")    # true
intRe.test("-1")       # true
intRe.test("+42")      # true
intRe.test("3.14")     # false

# With commas
const commaInt = /^[+-]?\d{1,3}(?:,\d{3})*$/;
commaInt.test("1,234")        # true
commaInt.test("12,345,678")   # true
commaInt.test("1234")         # false (needs comma)

# Allow comma-less too
^[+-]?(?:\d{1,3}(?:,\d{3})*|\d+)$

# Python
import re
re.match(r"^[+-]?\d+$", "12345")  # match

# Strip commas before parsing
"1,234,567".replace(/,/g, "")  # "1234567"
int("1,234,567".replace(",", ""))  # 1234567

# Hex integer
^0x[0-9a-fA-F]+$

# Octal
^0o?[0-7]+$

# Binary
^0b[01]+$

小数

^-?\d+\.\d+$ 匹配小数。可选小数部分: ^-?\d+(\.\d+)?$。科学计数法需额外处理 e/E: ^-?\d+(\.\d+)?[eE][-+]?\d+$。注意 .5 这种省略形式需用 ^-?\d*\.\d+$ 匹配。

regex
# Basic decimal: digits.digits
^\d+\.\d+$

# Optional integer or fractional part
^\d+\.?\d*$        # 1, 1., 1.5
^\d*\.?\d+$        # .5, 1.5, 1
^\d+\.\d+|\d+$    # integer or decimal

# With optional sign
^[+-]?(?:\d+\.\d+|\d+)$

# JavaScript
const decRe = /^[+-]?(?:\d+\.\d+|\d+)$/;
decRe.test("3.14")     # true
decRe.test("-0.5")     # true
decRe.test("1")        # true
decRe.test(".5")       # false (requires leading digit)

# Allow leading dot
^[+-]?(?:\d+\.?\d*|\.\d+)$

# Fixed decimal places (e.g., 2)
^[+-]?\d+\.\d{2}$

# With thousand separators
^[+-]?\d{1,3}(?:,\d{3})*\.\d+$

# Avoid trailing dot
^[+-]?\d+(?:\.\d+)?$

# Extract decimals from text
"The price is 3.14 dollars".match(/-?\d+\.\d+/)
// ["3.14"]

# Python
import re
re.findall(r"-?\d+\.\d+", "Pi is 3.14, e is 2.72")  # ['3.14', '2.72']

# Strict: 1-6 decimal places
^\d+\.\d{1,6}$

负数

^-\d+$ 匹配纯负数。包含零及正负: ^[+-]?\d+$。注意 -0 也是合法匹配,业务上可能需排除。货币场景常需 - 前置于符号前: ^\$-?\d+\.\d{2}$ 这种格式需根据具体需求调整。

regex
# Optional minus sign
^-?\d+$

# Optional + or -
^[+-]?\d+$

# Required minus
^-\d+$

# With decimal
^-?\d+\.\d+$

# JavaScript
const negRe = /^-?\d+$/;
negRe.test("-123")   # true
negRe.test("123")    # true (zero or one minus)
negRe.test("--1")    # false

# Strict negative integer
const strictNeg = /^-\d+$/;
strictNeg.test("-1")    # true
strictNeg.test("0")     # false
strictNeg.test("-0")    # true (technically -0)

# Exclude negative zero
^-(?!0+$)\d+$

# Allow scientific notation with sign
^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$

# Extract signed numbers from text
"Temperature: -5 to +10 degrees".match(/[+-]?\d+/g)
// ["-5", "+10"]

# Python
import re
re.findall(r"-?\d+", "loss: -100, gain: 50")  # ['-100', '50']

# Avoid matching minus as text hyphen
# Use word boundary or whitespace before
\s-?\d+      # minus must follow whitespace
\b-?\d+\b   # word boundary

# Currency with optional negative
^\$?-?\d+(?:\.\d{2})?$  # $-5.00 or -5.00 or $5.00

科学计数法

^-?\d+(\.\d+)?[eE][-+]?\d+$ 匹配如 -1.23e+4。指数部分必须有符号或数字:[eE][-+]?\d+。注意 1e4(无小数)也合法,需用 (\.\d+)? 让小数部分可选。Python 的 float() 接受更宽松的格式。

regex
# Scientific notation: mantissa e exponent
^[+-]?(?:\d+\.?\d*|\.\d+)[eE][+-]?\d+$

# Examples: 1e10, -2.5E-3, 6.022e23, 1.5E+10

# JavaScript
const sciRe = /^[+-]?(?:\d+\.?\d*|\.\d+)[eE][+-]?\d+$/;
sciRe.test("1e10")        # true
sciRe.test("-2.5E-3")     # true
sciRe.test("6.022e23")    # true
sciRe.test("1.5E+10")     # true
sciRe.test("1e")          # false (no exponent)

# Allow integer or decimal without exponent too
^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$

# Capture mantissa and exponent
^([+-]?(?:\d+\.?\d*|\.\d+))[eE]([+-]?\d+)$

const m = "-2.5E-3".match(/^([+-]?(?:\d+\.?\d*|\.\d+))[eE]([+-]?\d+)$/);
// m[1]="-2.5" (mantissa), m[2]="-3" (exponent)

# Convert to number
parseFloat("6.022e23")  # 6.022e+23
Number("1.5E+10")       # 15000000000

# Python
import re
re.match(r"^[+-]?(?:\d+\.?\d*|\.\d+)[eE][+-]?\d+$", "-2.5E-3")
float("-2.5E-3")  # -0.0025

# Extract scientific numbers from text
re.findall(r"[-+]?\d+\.?\d*[eE][-+]?\d+", "Avogadro: 6.022e23")
# ['6.022e23']

# Engineering notation (exponent multiple of 3)
# Hard to enforce via regex; better to validate after parsing
# Examples: 1.5e3, 2.5e6, 1e-3 (not 1.5e4)

货币格式

^\$?\d{1,3}(,\d{3})*(\.\d{2})?$ 匹配美元格式 $1,234.56。人民币 ¥1,234.56。欧元常用 1.234,56(逗号小数点对调)。多币种支持需按地区调整分隔符。生产环境建议用 Intl.NumberFormat 格式化和解析。

regex
# US currency: $123.45 or $1,234.56
^\$\d{1,3}(?:,\d{3})*\.\d{2}$

# Optional $ and cents
^\$?\d{1,3}(?:,\d{3})*(?:\.\d{2})?$

# Optional sign for negative
^[+-]?\$?\d{1,3}(?:,\d{3})*(?:\.\d{2})?$

# Allow negative after $: -$5.00 or $-5.00
^[+-]?\$[+-]?\d+(?:\.\d{2})?$

# European format: 1.234,56 €
^\d{1,3}(?:\.\d{3})*,\d{2}\s?€$

# JavaScript
const usd = /^\$\d{1,3}(?:,\d{3})*\.\d{2}$/;
usd.test("$1,234.56")   # true
usd.test("$123.45")     # true
usd.test("$1234.56")    # false (needs comma)
usd.test("$1.234")      # false (needs 2 decimals)

# Loose: just match dollar amount
/\$[\d,.]+/g

# Extract all currency from text
"Total: $1,234.56, tax: $99.99".match(/\$[\d,]+\.\d{2}/g)
// ["$1,234.56", "$99.99"]

# Parse to number
const parseUSD = (s) => parseFloat(s.replace(/[$,]/g, ""));
parseUSD("$1,234.56")  # 1234.56

# Format number as currency
(1234.56).toLocaleString("en-US", { style: "currency", currency: "USD" })
// "$1,234.56"

# Python
import re
re.match(r"^\$\d{1,3}(?:,\d{3})*\.\d{2}$", "$1,234.56")

# Bitcoin: 0.5 BTC, satoshis
^\d+(?:\.\d{1,8})?\s?(?:BTC|sat|sats)$
14

字符串替换

简单替换

str.replace(/foo/g, 'bar') 替换所有 'foo'。无 g 标志只替换第一个。简单字面量替换用 str.replaceAll('foo', 'bar') 更快且无需转义。注意替换字符串中的 $ 有特殊含义,字面 $ 需写为 $$。

regex
# Replace first occurrence

# JavaScript
"hello world".replace(/world/, "there")  # "hello there"
"hello world".replace(/o/, "0")          # "hell0 world" (first only)

# Python
import re
re.sub(r"world", "there", "hello world")  # "hello there"
re.sub(r"o", "0", "hello world", count=1)  # "hell0 world" (first only)

# Java
"hello world".replaceFirst("o", "0")    # "hell0 world"
"hello world".replace("world", "there") # literal, ALL occurrences

# PHP
preg_replace('/world/', 'there', 'hello world', 1)  # limit 1

# Replace literal string (no regex)
"hello.world".replace(".", " ")          # "hello world" (JS, literal)
"hello.world".replaceAll(".", " ")       # "hello world" (JS, literal)
# Python: str.replace is literal
"hello.world".replace(".", " ")          # "hello world"

# Replace using regex with special chars
"1.5".replace(/\./g, ",")               # "1,5" (escape dot)
"1+2=3".replace(/\+/g, "-")             # "1-2=3" (escape +)

# Chain replacements
"  Hello World  ".trim().replace(/\s+/g, "_").toLowerCase()
# "hello_world"

# Python chain
"_".join("  Hello World  ".strip().split()).lower()
# "hello_world"

替换全部

JS: replaceAll 需 g 标志,或 replace(/.../g, ...)。Python: re.sub 默认全部替换,用 count 限制。注意 replaceAll 在旧版浏览器不支持,可用 replace(/.../g, ...) 或 polyfill。性能上,简单字符串替换优于正则。

regex
# Replace ALL occurrences

# JavaScript: requires /g flag
"a-b-c-d".replace(/-/g, "+")          # "a+b+c+d"
"a-b-c-d".split("-").join("+")        # "a+b+c+d" (alternative)

# JavaScript: replaceAll (ES2021+)
"a-b-c-d".replaceAll("-", "+")        # "a+b+c+d" (string, not regex)
"a-b-c-d".replaceAll(/-/g, "+")       # needs /g flag with regex

# Python: re.sub does all by default
import re
re.sub(r"-", "+", "a-b-c-d")          # "a+b+c+d"

# Python: str.replace also does all
"a-b-c-d".replace("-", "+")           # "a+b+c+d" (literal)

# Java
"a-b-c-d".replaceAll("-", "+")        # regex, all
"a-b-c-d".replace("-", "+")           # literal, all

# PHP
preg_replace('/-/', '+', 'a-b-c-d')   # all

# Go
regexp.MustCompile("-").ReplaceAllString("a-b-c-d", "+")  # "a+b+c+d"

# Count occurrences
const count = (s, re) => (s.match(re) || []).length;
count("a-b-c-d", /-/g)  # 3

# Replace with limit (Python, PHP)
re.sub(r"-", "+", "a-b-c-d", count=2)      # "a+b+c-d"
preg_replace('/-/', '+', 'a-b-c-d', 2)     # "a+b+c-d"

# Global case-insensitive replace
"Hello hello HELLO".replace(/hello/gi, "hi")  # "hi hi hi"

替换中用捕获组

$1 $2 引用捕获组: '2024-01-01'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$2/$3/$1') 转为 MM/DD/YYYY。命名组用 $<name>。Python 用 \1 \2 或 \g<name>。注意 JS 用 $,Python 用 \,语法不同。

regex
# Use captured groups in replacement

# Swap two words
"hello world".replace(/^(\w+) (\w+)$/, "$2 $1")  # "world hello"

# Reformat date
"2024-01-15".replace(/(\d{4})-(\d{2})-(\d{2})/, "$3/$2/$1")
# "15/01/2024"

# Wrap matches
"hello".replace(/\w+/g, "[$&]")  # "[hello]"
"hello world".replace(/\w+/g, "[$&]")  # "[hello] [world]"

# Duplicate matches
"abc".replace(/(\w)/g, "$1$1")  # "aabbcc"

# Conditional-like behavior with alternation
"Mr Smith".replace(/(Mr|Ms|Mrs) (\w+)/, "$2, $1.")
# "Smith, Mr."

# Python: \1, \2 or \g<1>, \g<2> (preferred)
import re
re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\3/\2/\1", "2024-01-15")
# "15/01/2024"

re.sub(r"(\w+)", r"[\1]", "hello")  # "[hello]"

# Use named groups
"2024-01-15".replace(
  /(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/,
  "$<d>/$<m>/$<y>"
)  # "15/01/2024"

# Python named
re.sub(
  r"(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})",
  r"\g<d>/\g<m>/\g<y>",
  "2024-01-15"
)

# Reference whole match: $& (JS), $0 (Java), \g<0> (Python)
"abc".replace(/\w/g, "[$&]")  # "[a][b][c]"
re.sub(r"\w", r"[\g<0>]", "abc")  # "[a][b][c]"

# Literal $ in replacement (JS): use $$
"price".replace(/price/, "$$100")  # "$100"

大小写转换

JS 的 replace 配合函数可实现大小写转换: 'hello'.replace(/\w/g, m => m.toUpperCase())。Python 用 str.upper()/lower()/title()。正则的 \U \L \E(PCRE/Perl)可在替换串中转换大小写,但 JS 不支持。

regex
# Capitalize first letter of each word (title case)
"hello world".replace(/\b\w/g, c => c.toUpperCase())
# "Hello World"

# Capitalize first letter of string only
"hello world".replace(/^\w/, c => c.toUpperCase())
# "Hello world"

# Lowercase first letter, rest unchanged
"HelloWorld".replace(/^\w/, c => c.toLowerCase())
# "helloWorld"

# camelCase to snake_case
"myVariableName".replace(/([A-Z])/g, "_$1").toLowerCase()
# "my_variable_name"

# snake_case to camelCase
"my_variable_name".replace(/_([a-z])/g, (_, c) => c.toUpperCase())
# "myVariableName"

# kebab-case to camelCase
"my-variable-name".replace(/-([a-z])/g, (_, c) => c.toUpperCase())
# "myVariableName"

# camelCase to kebab-case
"myVariableName".replace(/([A-Z])/g, "-$1").toLowerCase()
# "my-variable-name"

# UPPER_CASE to lowerCase
"MY_VAR_NAME".replace(/_/g, "").toLowerCase()
# "myvarname" (without proper conversion)

# Constant case to camelCase
"MY_VAR_NAME".toLowerCase().replace(/_([a-z])/g, (_, c) => c.toUpperCase())
# "myVarName"

# Sentence case: capitalize first letter of each sentence
"hello. world. foo.".replace(/(^|\.\s+)([a-z])/g, (_, p, c) => p + c.toUpperCase())
# "Hello. World. Foo."

# Python equivalents
import re
re.sub(r"\b\w", lambda m: m.group().upper(), "hello world")
# "Hello World"

re.sub(r"([A-Z])", r"_\1", "myVar").lower()
# "my_var"

去除空白

str.replace(/^\s+|\s+$/g, '') 等价 str.trim()。去除中间多余空白: str.replace(/\s+/g, ' ')。去除全角空格需包含 \u3000。简单 trim 用 str.trim() 更快更易读,正则用于复杂空白清理。

regex
# Trim leading and trailing whitespace
"  hi  ".replace(/^\s+|\s+$/g, "")  # "hi"
# Note: JS String.prototype.trim() is preferred: "  hi  ".trim()

# Trim leading only
"  hi  ".replace(/^\s+/, "")  # "hi  "
# Or: "  hi  ".replace(/^\s+/g, "")

# Trim trailing only
"  hi  ".replace(/\s+$/, "")  # "  hi"

# Collapse multiple spaces into one
"a   b    c".replace(/\s+/g, " ")  # "a b c"

# Collapse but preserve newlines
"a   b

  c".replace(/[ \t]+/g, " ").replace(/\n{2,}/g, "\n")
# "a b
c"

# Remove all whitespace
"a b c".replace(/\s/g, "")  # "abc"
"a b c".replace(/\s+/g, "")  # "abc"

# Remove blank lines
"text\n\n\nmore".replace(/^\s*$(?:\n|$)/gm, "")
# "text
more"

# Normalize line endings
"text\r\nmore".replace(/\r\n/g, "\n")  # CRLF -> LF
"text\rmore".replace(/\r/g, "\n")        # CR -> LF

# Remove trailing whitespace from each line
"text  \nmore ".replace(/\s+$/gm, "")
# "text
more"

# Strip comments (/* */ and //)
code.replace(/\/\*[\s\S]*?\*\//g, "")  # block comments
code.replace(/\/\/.*$/gm, "")              # line comments

# Python equivalents
import re
re.sub(r"^\s+|\s+$", "", "  hi  ")  # "hi"
re.sub(r"\s+", " ", "a   b    c")    # "a b c"
re.sub(r"\s+$", "", "a   ", flags=re.M)  # trim trailing per line

移除 HTML 标签

str.replace(/<[^>]+>/g, '') 移除 HTML 标签。注意此正则不处理 <script> 内容、注释、CDATA 等。生产环境用 DOMParser 或专用 sanitize 库更安全,正则易被绕过,存在 XSS 风险,不应在安全敏感场景使用。

regex
# Naive: remove anything between < and >
"<p>Hello</p>".replace(/<[^>]+>/g, "")  # "Hello"

# Strip all tags but keep content
"<div><p>Hello <b>world</b></p></div>".replace(/<[^>]+>/g, "")
# "Hello world"

# Remove specific tag
"<div>keep</div>".replace(/<\/?div>/g, "")  # "keep"

# Remove script and style blocks (with content)
html.replace(/<script[\s\S]*?<\/script>/gi, "")
html.replace(/<style[\s\S]*?<\/style>/gi, "")

# Decode basic HTML entities
"AT&amp;T".replace(/&amp;/g, "&")
"&lt;tag&gt;".replace(/&lt;/g, "<").replace(/&gt;/g, ">")
"&nbsp;".replace(/&nbsp;/g, " ")
"&quot;".replace(/&quot;/g, '\"')

# Decode all entities via DOM (JS)
function decodeEntities(s) {
  const t = document.createElement("textarea");
  t.innerHTML = s;
  return t.value;
}

# Remove attributes from tags
'<a href="x" class="y">link</a>'.replace(/(<\w+)[^>]*>/g, "$1>")
# "<a>link</a>" (but the </a> stays; need to handle closing too)

# Self-closing tags
html.replace(/<\w+[^>]*/>/g, "")

# Strip HTML comments
html.replace(/<!--[\s\S]*?-->/g, "")

# Python
import re
re.sub(r"<[^>]+>", "", "<p>Hello</p>")  # "Hello"
re.sub(r"<script[\s\S]*?</script>", "", html, flags=re.I)

# WARNING: regex is fragile for HTML parsing.
# Use DOMParser (JS) or BeautifulSoup (Python) for real HTML.
15

提取与分割

提取第一个匹配

str.match(/pattern/) 返回第一个匹配及捕获组(无 g 标志)。exec() 行为类似。Python 用 re.search() 返回 Match 对象或 None。只需判断是否存在用 test()(JS)或 re.search()(Python)。

regex
# Extract first match

# JavaScript
const m = "price: $42.50, tax: $3.00".match(/\$(\d+\.\d{2})/);
// m[0] = "$42.50", m[1] = "42.50"
if (m) console.log(m[1]);  // "42.50"

# Python
import re
m = re.search(r"\$(\d+\.\d{2})", "price: $42.50, tax: $3.00")
if m:
    print(m.group(1))  # "42.50"

# Java
Matcher m = Pattern.compile("\\$(\\d+\\.\\d{2})")
                   .matcher("price: $42.50");
if (m.find()) { String price = m.group(1); }

# PHP
if (preg_match('/\$(\d+\.\d{2})/', 'price: $42.50', $m)) {
    echo $m[1];  // "42.50"
}

# Extract with named groups
const m2 = "2024-01-15".match(
  /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
);
if (m2) console.log(m2.groups.year);  // "2024"

# Test if pattern exists (no extraction needed)
const has = /\d{4}-\d{2}-\d{2}/.test("date: 2024-01-15");  // true

# Python: search vs match vs fullmatch
re.search(r"abc", "xabcx")   # finds anywhere
re.match(r"abc", "abcx")     # anchored at start
re.fullmatch(r"abc", "abc")  # entire string

提取所有匹配

str.match(/pattern/g) 返回所有匹配数组(无捕获组详情)。str.matchAll(/pattern/g) 返回迭代器,含捕获组详情(需 g 标志)。Python 用 re.findall() 返回列表,re.finditer() 返回迭代器。

regex
# Extract ALL matches

# JavaScript: with /g flag, match() returns array of matches (no groups)
"1a2b3c".match(/\d/g)        # ["1", "2", "3"]
"a1 b2 c3".match(/\d+/g)     # ["1", "2", "3"]

# matchAll (ES2020+) returns iterator with full match info
const matches = [..."2024-01-15 2025-02-20".matchAll(/(\d{4})-(\d{2})-(\d{2})/g)];
// matches[0][0]="2024-01-15", matches[0][1]="2024", ...

# Python: re.findall returns list of group(s)
import re
re.findall(r"\d+", "a1 b2 c3")            # ["1", "2", "3"]
re.findall(r"(\d+)-(\d+)", "12-34 56-78")  # [("12","34"), ("56","78")]
# Note: if groups present, returns tuples of groups, not whole match
# Use (?:...) or non-capturing to get whole match
re.findall(r"\d+-\d+", "12-34 56-78")    # ["12-34", "56-78"]

# re.finditer returns match objects (lazy iterator)
for m in re.finditer(r"\d+", "a1 b2 c3"):
    print(m.group(), m.start(), m.end())

# Java: Matcher.find() in loop
Matcher m = Pattern.compile("\\d+").matcher("a1 b2 c3");
while (m.find()) { System.out.println(m.group()); }

# PHP: preg_match_all fills multidimensional array
preg_match_all('/\d+/', 'a1 b2 c3', $m, PREG_SET_ORDER);
// $m[0][0] = "1", $m[1][0] = "2", ...

# Count matches
(len("a1 b2 c3".match(/\d/g) || []))  # 3 (JS)
len(re.findall(r"\d", "a1 b2 c3"))     # 3 (Python)

分割字符串

str.split(/[,\s]+/) 按逗号或空白分割。JS 的 split 会包含捕获组:'a,b'.split(/(,)/) 得 ['a', ',', 'b']。Python 的 re.split 行为类似。限制分割次数: JS split(sep, limit),Python re.split(sep, str, maxsplit)。

regex
# Split string by pattern

# JavaScript
"a,b,c".split(",")            # ["a", "b", "c"]
"a, b , c".split(/\s*,\s*/)  # ["a", "b", "c"] (trim around comma)
"1-2-3".split("-")            # ["1", "2", "3"]

# Split with capture group keeps the delimiter
"a1b2c3".split(/(\d)/)       # ["a", "1", "b", "2", "c", "3", ""]

# Limit splits
"a,b,c,d".split(",", 2)       # ["a", "b"] (rest in last element)
"a,b,c,d".split(/,/, 2)       # ["a", "b"]

# Python
import re
re.split(r"\s*,\s*", "a, b , c")  # ["a", "b", "c"]
re.split(r"(\d)", "a1b2c3")         # ["a", "1", "b", "2", "c", "3", ""]
re.split(r",", "a,b,c", maxsplit=1)  # ["a", "b,c"]

# Java
"a,b,c".split(",")            # ["a", "b", "c"]
"a, b , c".split("\\s*,\\s*")  # ["a", "b", "c"]
"a,b,c,d".split(",", 2)       # ["a", "b,c,d"]

# PHP
preg_split('/\s*,\s*/', 'a, b , c')  # ["a", "b", "c"]
preg_split('/(\d)/', 'a1b2c3', -1, PREG_SPLIT_DELIM_CAPTURE)

# Go
regexp.MustCompile("\\s*,\\s*").Split("a, b , c", -1)

# Split on whitespace (any)
"a   b\tc\nd".split(/\s+/)  # ["a", "b", "c", "d"]

# Split lines
"line1\nline2\r\nline3".split(/\r\n|\r|\n/)  # ["line1", "line2", "line3"]

提取键值对

用捕获组提取键值: str.match(/(\w+)=(\w+)/g)。matchAll 配合命名组更清晰: [...str.matchAll(/(?<key>\w+)=(?<value>\w+)/g)]。URL 查询参数解析建议用 URLSearchParams,比正则更健壮。

regex
# Extract key=value pairs

# Match key=value (or key: value)
(\w+)\s*[=:]\s*([^,;\n]+)

# JavaScript
const text = "name=Alice, age=30; city: NYC";
const pairs = [...text.matchAll(/(\w+)\s*[=:]\s*([^,;\n]+)/g)];
const obj = Object.fromEntries(pairs.map(m => [m[1], m[2].trim()]));
// { name: "Alice", age: "30", city: "NYC" }

# Python
import re
text = "name=Alice, age=30; city: NYC"
pairs = re.findall(r"(\w+)\s*[=:]\s*([^,;\n]+)", text)
dict(pairs)  # {"name": "Alice", "age": "30", "city": "NYC"}

# URL query string parsing
"?a=1&b=2&c=3".match(/[?&]([^=]+)=([^&]+)/g)
// ["?a=1", "&b=2", "&c=3"]

# Parse query string into object
const qs = "a=1&b=2&c=hello%20world";
const params = Object.fromEntries(
  [...qs.matchAll(/([^=&]+)=([^&]*)/g)]
    .map(m => [m[1], decodeURIComponent(m[2])])
);
// { a: "1", b: "2", c: "hello world" }

# Python equivalent
from urllib.parse import parse_qs
parse_qs("a=1&b=2&c=hello%20world")
# {"a": ["1"], "b": ["2"], "c": ["hello world"]}

# HTTP header parsing
const headers = "Content-Type: text/html\nAccept: */*";
const headerObj = Object.fromEntries(
  [...headers.matchAll(/^(\w[\w-]*):\s*(.+)$/gm)]
    .map(m => [m[1], m[2]])
);
// { "Content-Type": "text/html", "Accept": "*/*" }

词法分析

用正则做简单词法分析: str.match(/\d+\.\d+|\d+|[a-zA-Z_]+|[(){}]/g)。注意顺序重要: \d+\.\d+ 要在 \d+ 前,否则小数点会被切断。复杂词法分析建议用专用 lexer 工具(如 PLY、nearley),正则维护性差。

regex
# Tokenize: split text into tokens (words, numbers, punctuation)

# Simple word tokenization
\w+              # sequences of word chars
"Hello, world! 42".match(/\w+/g)  # ["Hello", "world", "42"]

# Tokenize keeping punctuation
/[A-Za-z0-9]+|[^\sA-Za-z0-9]/g
"Hello, world!".match(/[A-Za-z0-9]+|[^\sA-Za-z0-9]/g)
# ["Hello", ",", "world", "!"]

# Number tokens
/\d+\.?\d*|\.[0-9]+/
"Price 3.14 and .5".match(/\d+\.?\d*|\.[0-9]+/g)  # ["3.14", ".5"]

# String literals
/"([^"\\]|\\.)*"|'([^'\\]|\\.)*'/

# Programming language tokens (simplified)
const tokens = source.matchAll(
  /(?<comment>\/\/.*$|\/\*[\s\S]*?\*\/)
   |(?<string>"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')
   |(?<number>\d+\.?\d*)
   |(?<ident>[A-Za-z_]\w*)
   |(?<op>[+\-*/=<>!&|]+)
   |(?<punct>[(){}[\];,.])/gx
);

# Split into words preserving position
import re
for m in re.finditer(r"\w+", "Hello world foo"):
    print(m.group(), m.start(), m.end())

# Sentence tokenization (simplified)
text.split(/(?<=[.!?])\s+/)
# Splits on whitespace after . ! or ?

# Whitespace tokenization (preserves tokens)
text.split(/(\s+)/)  # keeps whitespace as separate elements
16

贪婪与懒量词

贪婪默认

默认贪婪: 尽量多匹配,再回溯。'.*' 匹配 '<a><b>' 整体而非 '<a>'。贪婪量词先尝试最大匹配,失败后逐步回退。在无回溯的简单场景下,贪婪通常更快;在可能过度匹配时,需用懒量词或更精确的字符类。

regex
# Quantifiers are GREEDY by default - match as much as possible
# * + ? {n,m} all greedy

# Greedy: .* takes everything, then backtracks
"<a> <b> <c>".match(/<.*>/)   # ["<a> <b> <c>"] (whole thing!)

# Greedy backtracking example
"12345".match(/\d+\d/)   # \d+ takes "1234", last \d takes "5"

# Greedy with anchored end - takes everything then backtracks to find end
"hello world end".match(/^.*(end).*$/)  # captures "end"

# Common greedy patterns
.*            # entire line (zero or more)
.+            # entire line (one or more)
\d+           # all consecutive digits
\w+           # all consecutive word chars
\s+           # all consecutive whitespace

# HTML gotcha: greedy matches too much
"<b>bold</b> and <i>italic</i>".match(/<.+>/)
# ["<b>bold</b> and <i>italic</i>"] - WRONG, matches across tags!

# Greedy on alternation
"cat or dog".match(/cat|cat dog/)  # ["cat"] - first alternative wins

# Email greedy
"a@b@c".match(/.+@.+/)  # ["a@b@c"] - greedy .+ takes "a@b"

懒量词

在量词后加 ? 使其变懒: .*? .+? .{n,m}? 尽量少匹配。'<.*?>' 匹配 '<a>' 而非 '<a><b>'。懒量词从最小匹配开始,逐步扩展。并非总比贪婪快——若目标在末尾,懒量词需多次扩展,反而更慢。

regex
# Add ? after a quantifier to make it LAZY (non-greedy)
# Lazy: match as LITTLE as possible, expand only if needed

# Lazy quantifiers
*?     # lazy zero or more
+?     # lazy one or more
??     # lazy zero or one
{n,m}? # lazy range

# Lazy: stops at FIRST closing tag
"<a> <b> <c>".match(/<.*?>/)   # ["<a>"] - just the first tag
"<b>bold</b> <i>italic</i>".match(/<.+?>/)
# ["<b>"] - just the first opening tag

# Find quoted strings
'"a" and "b"'.match(/".*?"/g)   # ['"a"', '"b"']
'"a" and "b"'.match(/".*"/g)    # ['"a" and "b"'] (greedy wrong!)

# Capture content between tags
"<div>hello</div>".match(/<div>(.*?)<\/div>/)  # captures "hello"

# Lazy vs negated class (often faster alternative)
# These are equivalent but negated class is usually faster:
'"hello"'.match(/"[^"]*"/)     # uses negated class - preferred
'"hello"'.match(/".*?"/)       # uses lazy - works but slower

# Multiple lazy quantifiers
'(.*?)(.*?)(.*?)'  # all lazy, but each expands as needed

# When greedy fails, lazy often helps
# Match XML comments <!-- ... -->
text.match(/<!--[\s\S]*?-->/g)  # lazy: stops at first -->

常见懒模式

常见懒模式: .*? 任意字符最少、\d+? 数字最少、\s+? 空白最少。注意 .*? 可能匹配空串,需根据场景用 .+?。HTML 标签匹配 <[^>]+> 比 <.*?> 更高效且不会跨标签匹配。

regex
# Most common lazy patterns

# Content between tags (HTML/XML)
<([a-z][a-z0-9]*)\b[^>]*>(.*?)<\/\1>
# captures tag name and inner content (lazy)

# Quoted strings
"([^"]*)"        # double-quoted (preferred over ".*?")
'([^']*)'        # single-quoted

# Bracketed content
\[([^\]]*)\]   # content inside [ ]
\(([^()]*)\)   # content inside ( )
\{([^{}]*)\}   # content inside { }

# Comments
/\*[\s\S]*?\*/   # C-style block comment
<!--.*?-->        # HTML comment (with /s for multiline)

# URL in text
https?://\S+?["'\s<>]   # URL until delimiter

# Path components
/([^/]+)         # one path segment

# Code block in markdown
\`\`\`([\s\S]*?)\`\`\`   # fenced code block

# Lazy with anchors for validation
^.*?\bword\b.*$  # line containing "word" (lazy doesn't matter here)

# Variable assignment
(\w+)\s*=\s*("[^"]*"|'[^']*'|\d+)

何时用哪种

选择原则: 目标靠近起始用懒,靠近末尾用贪婪;避免 .* 跨界匹配,优先用否定字符类 [^"]* 匹配引号内内容。性能: [^<]* 比 .*? 快(无回溯)。可读性: 否定字符类比懒量词意图更清晰。

regex
# Use GREEDY when:
# - You want the longest possible match
# - The end is uniquely identified
# - You want to capture until the LAST occurrence

# Capture everything to the last occurrence of X
^.*X         # greedy: matches up to the LAST X
"everything;until;last;semicolon".match(/^.*;/)  # "everything;until;last;"

# Use LAZY when:
# - You want the shortest match
# - Multiple delimiters exist
# - You want to match each occurrence separately

# Match each quoted string
'"a","b","c"'.match(/"[^"]*"/g)   # ['"a"', '"b"', '"c"'] - non-lazy, negated
'"a","b","c"'.match(/".*?"/g)     # same result with lazy

# When neither works well: use specific character classes
# Match content of <div> (allowing nested non-div tags)
<div\b[^>]*>((?:(?!<div\b)[\s\S])*)<\/div>

# Practical: extract first URL from text
const text = 'visit https://example.com or https://other.com';
const m = text.match(/https?:\/\/[^\s<>"]+/i);
// m[0] = "https://example.com" (greedy on char class, stops at space)

# Use anchored patterns to avoid greediness issues
^.*?word.*$    # lazy doesn't help here - anchored greedy works fine

# Performance: greedy can be FASTER when no backtracking needed
\d+           # greedy but no backtrack issue (concrete chars)
.*?word        # lazy to avoid grabbing too much

灾难性回溯

嵌套量词如 (a+)+b 在输入 'aaaa...' (无 b)时会导致指数级回溯。防御: 用原子组 (?>...)、占有量词 a++、更具体的模式、设超时(Python re.timeout,JS 不支持)、用 RE2(线性时间,无回溯)。

regex
# Catastrophic backtracking: exponential time on certain inputs
# Caused by nested quantifiers with overlapping alternatives

# DANGEROUS: (a+)+ on "aaaaaaaaaaaaaaaaaaaaaaa!"
(a+)+         # nested quantifier - exponential backtracking on no-match

# DANGEROUS: overlapping alternatives
(a|a)*b       # tries many combinations before failing

# DANGEROUS: classic email regex
([a-zA-Z0-9._%+-]+@([a-zA-Z0-9.-]+\.)+[a-zA-Z]{2,})+
# nested + with alternation can blow up

# How to detect: test with long inputs that DON'T match
# If it hangs on "aaaaaaa...!" - you have it

# Fixes:
# 1. Use atomic groups (PCRE, Java, .NET - NOT JS)
(?>a+)+       # atomic group - no backtracking into group

# 2. Use possessive quantifiers (Java, PCRE - NOT JS)
a++           # possessive - no backtracking

# 3. Refactor: avoid overlapping alternatives
# Bad:  (a|a)*b
# Good: a*b

# 4. Be specific with character classes
# Bad:  (.*)*
# Good: [a-z]*  (or whatever specific chars)

# 5. Use unrolling the loop technique
# Replace (?:a*b*)+ with a*(?:ba*b)*  (for example)

# 6. Set a timeout (Python 3.11+)
import re
re.compile(r"(a+)+").match("a" * 30 + "b", timeout=1.0)

# 7. Use RE2 (linear-time engine) in Go, or Google's re2 library
# RE2 doesn't support backreferences but is immune to this issue

# Test your regex at regex101.com - it warns about catastrophic patterns
17

反向引用

编号反向引用

\1 \2 ... 引用对应编号的捕获组匹配内容。(\w+)\s+\1 匹配重复单词如 'hello hello'。反向引用要求前后内容完全相同。注意 RE2 不支持反向引用,Go 标准库正则也不支持。

regex
# \1, \2, ... refer to a PREVIOUSLY captured group's match
# The backreference must match the SAME text the group captured

# Find repeated word
\b(\w+)\s+\1\b
# Matches: "hello hello", "the the", "is is"

# Match HTML/XML tag pairs
<([a-z][a-z0-9]*)\b[^>]*>[\s\S]*?</\1>
# <div>...</div>, <p>...</p>, <span class="x">...</span>

# Match paired quotes (single or double)
(["\'])([^\1]*?)\1
# "hello" or 'hello' - same opening and closing quote

# Date with consistent separator
(\d{4})([-/])(\d{2})\2(\d{2})
# 2024-01-15 OK (same -)
# 2024-01/15 REJECTED (different separators)

# Palindrome-ish: abba
(\w)(\w)\2\1

# JavaScript
"hello hello world".match(/\b(\w+)\s+\1\b/)  # ["hello hello", "hello"]
"<div>x</div>".match(/<([a-z]+)>([\s\S]*?)<\/\1>/)  # ["<div>x</div>", "div", "x"]

# Python
import re
re.search(r"\b(\w+)\s+\1\b", "hello hello")  # match
re.search(r"<([a-z]+)>[\s\S]*?</\1>", "<b>x</b>")  # match

# Backreferences in replacement (different from in-pattern backrefs)
"hello".replace(/(\w)/g, "$1$1")  # "hheelllloo"

命名反向引用

\k<name> 或 \k'name' 引用命名组。(?<quote>['"]).*?\k<quote> 匹配成对引号(单或双)。命名反向引用比编号更易读,尤其在多个组时。JS/Python/PCRE/Java 7+ 均支持,语法略有差异。

regex
# Named capture group + named backreference

# JavaScript: (?<name>...) then \k<name>
(?<word>\w+)\s+\k<word>
(?<tag>[a-z]+)>[\s\S]*?</\k<tag>>

# Python: (?P<name>...) then (?P=name)
(?P<word>\w+)\s+(?P=word)
(?P<tag>[a-z]+)>[\s\S]*?</(?P=tag)>

# .NET: (?<name>...) then \k<name> (same as JS)
# Java: (?<name>...) then \k<name>
# PHP/PCRE: (?P<name>...) then (?P=name) or \k<name>

# JavaScript example
const re = /\b(?<word>\w+)\s+\k<word>\b/;
"the the cat".match(re)  # ["the the", "the"]

# Python example
import re
re.search(r"(?P<word>\w+)\s+(?P=word)", "hello hello")  # match

# Practical: match HTML with named groups
const html = '<div class="x"><span>hi</span></div>';
const re2 = /<(?<tag>[a-z][a-z0-9]*)\b[^>]*>(?<content>[\s\S]*?)<\/\k<tag>>/;
const m = html.match(re2);
// m.groups.tag = "div", m.groups.content = '<span>hi</span>'

# Quote matching with named group
const quoteRe = /(?<q>["\'])[^\k<q>]*\k<q>/g;
'text "hello" and \'world\''.match(quoteRe)  # ['"hello"', "\'world\'"]

# Named backref in replacement (JS)
"hello hello".replace(/(?<w>\w+)\s+\k<w>/, "$<w>")  # "hello"

重复单词

\b(\w+)\s+\1\b 匹配连续重复单词。加 i 标志可忽略大小写(The the)。注意 \1 引用的是捕获组匹配的具体内容,不是模式本身。编辑器中常用此模式检测文本中的笔误。

regex
# Find repeated (doubled) words: "the the", "is is"
\b(\w+)\s+\1\b

# Case-insensitive
/\b(\w+)\s+\1\b/i

# JavaScript
const dupRe = /\b(\w+)\s+\1\b/gi;
"I went to the the store and and bought milk".match(dupRe)
# ["the the", "and and"]

# Python
import re
re.findall(r"\b(\w+)\s+\1\b", "the the cat", re.IGNORECASE)
# ["the"]

# Fix doubled words (replace with single)
"I went to the the store".replace(/\b(\w+)\s+\1\b/gi, "$1")
# "I went to the store"

# Python
re.sub(r"\b(\w+)\s+\1\b", r"\1", "the the cat", flags=re.I)
# "the cat"

# Find triple repeats
\b(\w+)(\s+\1){2}\b  # "blah blah blah"

# Find any consecutive repeats
\b(\w+)(?:\s+\1)+\b  # "blah blah blah blah"

# Limit to specific words
\b(the|and|or|is)\s+\1\b  # only common stop words

# Find non-trivial repeats (3+ letters)
\b(\w{3,})\s+\1\b  # excludes short words like "is is"

# Skip common valid doubles (very rare in English)
# "had had", "that that" - use negative lookahead to exclude
\b(?!had|that)(\w+)\s+\1\b

HTML 标签匹配

<([a-z][a-z0-9]*)\b[^>]*>[\s\S]*?</\1> 匹配配对标签如 <div>...</div>。\1 确保开闭标签名一致。注意此正则不能处理嵌套标签(需递归正则),生产环境用 DOM 解析器更可靠。

regex
# Match opening and closing tag with backreference
<([a-z][a-z0-9]*)\b[^>]*>[\s\S]*?</\1>
# <div>...</div>, <span class="x">...</span>

# JavaScript
const html = '<div class="x"><p>hello</p></div>';
const re = /<([a-z][a-z0-9]*)\b[^>]*>([\s\S]*?)<\/\1>/gi;
const m = html.match(re);
// ["<div class="x"><p>hello</p></div>"]

# Iterate over all top-level tags
const re2 = /<([a-z][a-z0-9]*)\b[^>]*>([\s\S]*?)<\/\1>/gi;
let m2;
while ((m2 = re2.exec(html)) !== null) {
  console.log(m2[1], m2[2]);
}

# Python
import re
re.findall(r"<([a-z][a-z0-9]*)\b[^>]*>([\s\S]*?)</\1>",
           html, re.IGNORECASE)
# [("div", "<p>hello</p>")]

# Self-closing tags
<([a-z][a-z0-9]*)\b[^>]*/>

# Both opening and self-closing
<([a-z][a-z0-9]*)\b[^>]*(?:/>|>[\s\S]*?</\1>)

# Specific tag
<div\b[^>]*>([\s\S]*?)</div>

# WARNING: backreferences can't handle nested same-name tags
# <div><div>x</div></div> - regex can't match outer properly
# Use a parser for nested HTML

# Attribute extraction
<(?<tag>[a-z]+)(?<attrs>[^>]*)>(?<content>[\s\S]*?)</\k<tag>>

引号匹配

(['"])(.*?)\1 匹配成对单或双引号内容。\1 引用开头捕获的引号类型,确保前后一致。注意这与 ['"].*?['"] 不同: 后者会匹配 'abc" 这种不配对的情况。捕获组确保了配对。

regex
# Match a quoted string with CONSISTENT quote char
(["\'])([^\1]*?)\1
# "hello" or 'hello' - opening quote remembered, closing must match

# JavaScript
const str = 'She said "hello" and he said \'hi\'';
const re = /(["\'])([^"\']*?)\1/g;
str.match(re)  # ['"hello"', "\'hi\'"]

# With named group (clearer)
const re2 = /(?<q>["\'])(?<content>[^\k<q>]*?)\k<q>/g;

# Handle escaped quotes inside
# Allow \" inside double quotes, \' inside single
(["\'])(?:\\.|(?!\1)[^\\])*\1
# "hello \"world\"" matches correctly

# Python
import re
re.findall(r'(["\'])([^\1]*?)\1', 'She said "hello"')
# [('"', 'hello')]

# Practical: extract all quoted strings
text.match(/(["\'])(?:(?!\1).)*\1/g)

# Triple-quoted strings (Python-like)
(?<="""|''')[\s\S]*?\1

# Backtick strings (JS template literals)
`(?:[^\`\\]|\\.)*`

# Replace quoted content
'"hello world"'.replace(/"([^"]*)"/, "[$1]")  # "[hello world]"

# WARNING: this naive pattern fails on escaped quotes:
# "hello \"world\"" - the \" ends the match early
# Use the escape-aware version above for production
18

Unicode 正则

Unicode 模式

JS 的 u 标志启用 Unicode 模式: 正确处理代理对(emoji 等超出 BMP 的字符),. 匹配完整码点。无 u 标志时,emoji 如 𝐀(\uD835\uDC00)被当作两个字符。处理非 ASCII 文本务必加 u 标志。

regex
# JavaScript: /u flag for Unicode mode (ES6+)
/^.$/u.test("𝌆")    # true (one code point)
/^.$/.test("𝌆")     # false (two UTF-16 code units without /u)

# Without /u: surrogate pairs broken
"𝌆".length          # 2 (surrogate pair)
/[\uD834\uDF06]/u.test("𝌆")  # true (with /u, treats as one code point)

# Python 3: Unicode is default
import re
re.search(r"\w+", "日本語")  # matches (Unicode by default)
# re.ASCII or re.A for ASCII-only
re.search(r"\w+", "日本語", re.ASCII)  # ASCII-only, no match

# PHP: /u flag for UTF-8 mode
preg_match('/^.$/u', '𝌆')  # 1 (match, treats as one code point)

# Java: Pattern.UNICODE_CHARACTER_CLASS
Pattern p = Pattern.compile("\\w+", Pattern.UNICODE_CHARACTER_CLASS);

# .NET: Unicode by default
# Go (RE2): Unicode by default

# IMPORTANT: /u changes quantifier behavior in JS
/[😂]{2}/u.test("😂😂")  # true (quantifier on code point)
/[😂]{2}/.test("😂😂")   # unpredictable (code units)

# Case-insensitive Unicode (with /iu)
/\w/iu.test("Ω")  # true (Σ/σ/ς equivalence)

# Validate astral plane characters (emoji, ancient scripts)
/^[\u{1F300}-\u{1F9FF}]+$/u  # emoji range
/[\u{1F600}-\u{1F64F}]/u     # emoticons

属性转义 \p{...}

\p{L} 匹配任意字母,\p{N} 匹配任意数字,\p{Emoji} 匹配 emoji。需配合 u 标志。\P{...} 是取反版本。常见属性: L(字母)、N(数字)、S(符号)、P(标点)、Z(分隔符)。完整列表见 Unicode 标准。

regex
# Unicode property escapes (require /u in JS)

# \p{L} or \p{Letter} - any letter (any script)
/^\p{L}+$/u.test("Hello")       # true
/^\p{L}+$/u.test("日本語")       # true (CJK letters)
/^\p{L}+$/u.test("Привет")      # true (Cyrillic)

# \p{N} or \p{Number} - any number
/^\p{N}+$/u.test("123")         # true
/^\p{N}+$/u.test("١٢٣")         # true (Arabic-Indic digits)

# \p{P} or \p{Punctuation}
/^\p{P}+$/u.test("!?.")         # true

# \p{S} or \p{Symbol}
/^\p{S}+$/u.test("$€¥")         # true (currency symbols)

# \p{Z} or \p{Separator} (whitespace including Unicode)
/^\p{Z}+$/u.test(" \u3000")    # true (incl. ideographic space)

# Negation: \P{...} (uppercase P)
/^\P{L}+$/u.test("123!")        # true (no letters)

# Specific scripts
\p{Script=Han}      # CJK characters
\p{Script=Latin}    # Latin alphabet
\p{Script=Greek}    # Greek
\p{Script=Cyrillic} # Russian etc.

# Categories (long form)
\p{Letter}    # same as \p{L}
\p{Lowercase_Letter}  # same as \p{Ll}
\p{Uppercase_Letter}  # same as \p{Lu}

# Emoji
\p{Emoji}     # any emoji character
\p{Emoji_Presentation}  # emoji with emoji presentation

# JavaScript support: ES2018+
# Python: needs 'regex' module (PyPI) or Python 3.13+
# PHP: /u flag with \p{L} etc.
# Java: \p{L} (Unicode by default)
# .NET: \p{L} by default
# Go (RE2): \p{L} by default

字母属性 \p{L}

\p{L} 匹配所有语言的字母(含中文、日文、韩文等)。细分类: \p{Lu} 大写,\p{Ll} 小写,\p{Lt} 首字母大写,\p{Lo} 其他(含中文)。比 [a-zA-Z] 更全面,适合多语言文本处理。

regex
# \p{L} matches any letter in any script

# Match any word (Unicode-aware)
/\p{L}+/gu          # matches "hello", "日本語", "Привет"

# Match a single Latin letter
/\p{Script=Latin}/u

# Specific letter categories
\p{Lu}  # uppercase letter (A, B, Ω, Ψ)
\p{Ll}  # lowercase letter (a, b, ω, ψ)
\p{Lt}  # titlecase letter (Dž, Lj - rare)
\p{Lm}  # modifier letter (ˆ, ˇ)
\p{Lo}  # other letter (CJK, Arabic, Hebrew)

# JavaScript examples
"Hello Ωμέγα".match(/\p{L}+/gu)  # ["Hello", "Ωμέγα"]
"hello WORLD".match(/\p{Lu}+/gu)  # ["WORLD"]
"Hello".match(/\p{Ll}+/gu)        # ["ello"]

# Validate Unicode identifier
/^[\p{L}_][\p{L}\p{N}_]*$/u

# Match Capitalized Unicode word
/\p{Lu}\p{Ll}*/u   # "Hello", "Ωμέγα" (if Ω is uppercase)

# Count letters
const text = "Hello 世界 123";
const letters = text.match(/\p{L}/gu) || [];
// ["H", "e", "l", "l", "o", "世", "界"]

# Python (with 'regex' module)
import regex
regex.findall(r"\p{L}+", "Hello 世界")

# Split on non-letters (Unicode-aware)
"hello, world!世界".split(/\P{L}+/u)  # ["hello", "world", "世界"]

数字属性 \p{N}

\p{N} 匹配所有数字字符(含全角数字、罗马数字等)。\p{Nd} 仅匹配十进制数字(含全角 0-9)。注意 \d 在无 u 标志时仅匹配 ASCII [0-9],有 u 标志时等价 \p{Nd}。需精确控制时显式用 [0-9] 或 \p{Nd}。

regex
# \p{N} matches any number character in any script

# Match digits in any script
\p{N}+   # 123, ١٢٣ (Arabic-Indic), १२३ (Devanagari), etc.

# Subcategories
\p{Nd}  # decimal digit (0-9, ٠-٩ Arabic, ०-९ Devanagari)
\p{Nl}  # letter number (Roman numerals Ⅷ, etc.)
\p{No}  # other number (fractions ½, superscripts ²)

# JavaScript
"Price: 123, Arabic: ١٢٣".match(/\p{N}+/gu)
# ["123", "١٢٣"]

# Match only ASCII digits
/[0-9]/         # ASCII digit
/\d/           # in JS without /u: same as [0-9]
/\d/u          # with /u: matches Unicode digits too!

# ASCII-only number (always)
/[0-9]+/

# Match decimals (with optional decimal point)
/\p{N}+(?:\.\p{N}+)?/u

# Count digits (any script)
const text = "123 ١٢٣";
const digits = text.match(/\p{Nd}/gu) || [];
// ["1", "2", "3", "١", "٢", "٣"]

# Python
import regex
regex.findall(r"\p{N}+", "123 ١٢٣")

# Extract version numbers (ASCII only)
const v = "Version 1.2.3 released";
v.match(/[0-9]+(?:\.[0-9]+)+/)[0]  # "1.2.3"

# Currency with Unicode digits
/\p{Sc}\p{Nd}+(?:\.\p{Nd}+)?/u  # $123.45, €١٢٣.٤٥

Emoji 匹配

Emoji 匹配复杂:含修饰符、ZWJ 序列、肤色等。简单版 \p{Emoji} 匹配单个 emoji 字符。完整匹配需考虑 ZWJ(\u200D)序列和修饰符。生产环境建议用 emoji-regex 等专用库,正则难以覆盖所有情况。

regex
# Match emoji (requires /u flag in JS)

# Simple emoji property
/\p{Emoji}/u           # any emoji character
/^\p{Emoji}+$/u.test("👋😀🎉")  # true

# Emoji with presentation (displayed as emoji, not text)
/\p{Emoji_Presentation}/u

# Common emoji ranges (for older engines without \p{Emoji})
/[\u{1F600}-\u{1F64F}]/u  # emoticons
/[\u{1F300}-\u{1F5FF}]/u  # symbols & pictographs
/[\u{1F680}-\u{1F6FF}]/u  # transport & map
/[\u{1F700}-\u{1F77F}]/u  # alchemical
/[\u{2600}-\u{26FF}]/u    # misc symbols (☀ ☂ ☎)
/[\u{2700}-\u{27BF}]/u    # dingbats (✁ ✂ ✃)

# Combined range
/[\u{1F300}-\u{1F9FF}\u{2600}-\u{27BF}]/u

# Count emoji in text
const text = "Hello 👋😀 world 🎉";
const emoji = text.match(/\p{Emoji}/gu) || [];
// ["👋", "😀", "🎉"]

# Strip emoji from text
text.replace(/\p{Emoji}/gu, "")  # "Hello  world "

# Emoji with modifiers (skin tones, ZWJ sequences)
# 👨‍👩‍👧 is actually multiple code points joined by ZWJ (\u200D)
/\p{Emoji}(?:\u200D\p{Emoji})*/u
# matches a ZWJ sequence as one emoji

# Emoji with variation selector (\uFE0F)
/\p{Emoji}\uFE0F?/u  # optional variation selector

# Flag emoji (regional indicators)
/[\u{1F1E6}-\u{1F1FF}]{2}/u  # 🇺🇸, 🇨🇳, etc.

# JavaScript support: ES2018+ (\p{Emoji})
# Python: needs 'regex' module
# PHP: /u flag with \p{Emoji}
19

语言差异

JavaScript

JS 正则用 /pattern/flags 字面量或 RegExp 构造。ES2018+ 支持预查反向、s 标志、命名组。ES2025 提案支持修饰符。注意 String.match 与 RegExp.exec 行为差异,g 标志下 test/exec 有 lastIndex 状态。

regex
# JavaScript regex features

# Literal syntax (preferred for static patterns)
const re = /pattern/flags;

# Constructor (for dynamic patterns)
const re2 = new RegExp("pattern", "flags");

# Flags: g (global), i (case-insensitive), m (multiline),
#        s (dotAll, ES2018+), u (Unicode), y (sticky)

# Methods
/pattern/.test(string)             # boolean
string.match(/pattern/)            # match info or array
string.match(/pattern/g)           # array of matches (no groups)
[...string.matchAll(/pattern/g)]   # iterator (ES2020+)
string.replace(/pattern/, "repl")  # replace first
string.replace(/pattern/g, "repl") # replace all
string.split(/pattern/)            # split
string.search(/pattern/)           # index of first match or -1

# Named groups (ES2018+)
const m = "2024-01-15".match(/(?<y>\d{4})/);
m.groups.y  # "2024"

# Lookbehind (ES2018+, not supported in old Safari)
/(?<=\$)\d+/.test("$100")  # true

# Unicode property escapes (ES2018+)
/^\p{L}+$/u.test("Hello")  # true

# Gotcha: /g with exec() maintains lastIndex
const r = /a/g;
r.exec("aaa"); r.lastIndex  # 1
r.exec("aaa"); r.lastIndex  # 2
# Use matchAll or reset lastIndex to 0

# String.prototype.replaceAll (ES2021+)
"a-b-c".replaceAll("-", "+")  # "a+b+c"

# Escape function for literal strings
function escapeRegExp(s) {
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

Python

Python 用 re 模块,正则为字符串(需双重转义)或 r'...' 原始字符串。re.sub 默认全部替换,re.search 匹配任意位置,re.match 仅匹配开头。Python 3.11+ 支持 re.timeout 防灾难性回溯。regex 库支持更多特性(变长预查反向)。

regex
# Python regex (re module)

import re

# Raw strings (r"...") avoid double-escaping
pattern = r"\bword\b"
re.search(pattern, text)  # find anywhere
re.match(pattern, text)   # anchored at start
re.fullmatch(pattern, text)  # entire string

# Flags (constants)
re.IGNORECASE  # or re.I - case-insensitive
re.MULTILINE   # or re.M - ^ $ match line boundaries
re.DOTALL      # or re.S - . matches newline
re.VERBOSE     # or re.X - allow whitespace and comments
re.ASCII       # or re.A - ASCII-only for \w \d \s
re.UNICODE     # default in Python 3

# Combine flags with |
re.search(pattern, text, re.IGNORECASE | re.MULTILINE)

# Inline flags
(?i)pattern    # case-insensitive
(?im)pattern   # multiple inline flags
(?i:pattern)   # scoped (Python 3.6+)

# Common functions
re.search(pattern, string, flags)  # Match or None
re.match(pattern, string, flags)   # Match or None (anchored)
re.fullmatch(pattern, string)      # Match or None (entire)
re.findall(pattern, string)        # list of matches or tuples
re.finditer(pattern, string)       # iterator of Match objects
re.sub(pattern, repl, string, count=0)  # replace
re.split(pattern, string, maxsplit=0)   # split

# Named groups (Python-specific syntax)
(?P<name>pattern)        # capture
(?P=name)                # backreference
\g<name>                 # in replacement

# Compiled pattern (faster for reuse)
p = re.compile(r"\d+", re.IGNORECASE)
p.search("abc 123")

# Timeout (Python 3.11+)
re.compile(r"(a+)+").match(long_string, timeout=1.0)

# Third-party 'regex' module adds: \p{...}, variable lookbehind, recurs

Java

Java 用 Pattern.compile() 和 Matcher。字符串中反斜杠需双重转义 \\d。支持命名组 (?<name>...) 和 \k<name>。Pattern.UNICODE_CHARACTER_CLASS 启用 Unicode 模式(\d 等价 \p{Nd})。注意 Java 正则编译较慢,应复用 Pattern 对象。

regex
# Java regex (java.util.regex)

import java.util.regex.*;

# Compile pattern (double-escape backslashes in Java strings)
Pattern p = Pattern.compile("\\bword\\b");
Matcher m = p.matcher("a word here");

# Methods
m.find()        # finds next match (boolean)
m.matches()     # entire string matches (boolean)
m.lookingAt()   # matches at start (boolean)
m.group()       # entire match
m.group(1)      # first capture group
m.start()       # start index
m.end()         # end index

# Replace
m.replaceAll("repl")       # all matches
m.replaceFirst("repl")     # first match only

# String convenience methods (compile internally)
"abc".matches("a.c")           # true (entire string)
"a-b-c".replaceAll("-", "+")   # "a+b+c" (regex)
"a-b-c".replace("-", "+")      # "a+b+c" (literal)
"a-b-c".split("-")             # ["a", "b", "c"]

# Flags (constants)
Pattern.CASE_INSENSITIVE  # or 1 << 1
Pattern.MULTILINE         # or 1 << 2
Pattern.DOTALL            # or 1 << 3
Pattern.UNICODE_CHARACTER_CLASS  # or 1 << 7
Pattern.COMMENTS          # or 1 << 4

# Combine flags with |
Pattern.compile("abc", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE)

# Inline flags
(?i)abc       # case-insensitive
(?id)abc      # multiple
(?i:abc)      # scoped

# Named groups (Java 7+)
(?<name>pattern)
\k<name>      # backreference
${name}       # in replacement

# Lookbehind: must be FIXED length
(?<=ab|cd)x   # OK (same length)
(?<=ab|abc)x  # ERROR (different length)

# Possessive quantifiers (Java supports)
a++  a*+  a?+  a{2,5}+

# Atomic groups
(?>a|b)

# Stream matches (Java 9+)
p.matcher(input).results().forEach(m -> ...);

PHP (PCRE)

PHP 用 preg_ 系列函数: preg_match、preg_match_all、preg_replace。正则需用定界符如 /.../ 或 ~...~。PCRE 功能丰富:支持递归 (?R)、原子组、占有量词、子程序调用。PHP 7+ 用 PCRE2,部分语法略有变化。

regex
# PHP regex (PCRE - Perl-Compatible Regular Expressions)

# Pattern delimited by / (or other chars like #, ~, |)
/^[a-z]+$/i
'~^[a-z]+$~i'

# Functions
preg_match('/pattern/', $subject)            # 0 or 1 (bool-ish)
preg_match('/pattern/', $subject, $matches)  # fills $matches
preg_match_all('/pattern/', $subject, $matches)  # count, fills $matches
preg_replace('/pattern/', 'repl', $subject)  # replace all
preg_replace_callback('/pattern/', fn, $subject)  # function replace
preg_split('/pattern/', $subject)            # split

# Flags after closing delimiter
i  # case-insensitive
m  # multiline (^ $ at line boundaries)
s  # dotall (. matches newline)
x  # extended (whitespace ignored, # comments)
u  # treat pattern and subject as UTF-8

# Combine: /pattern/imsu

# $matches structure
preg_match('/(\w+)@(\w+)/', 'user@host', $m);
# $m[0] = "user@host" (whole match)
# $m[1] = "user" (group 1)
# $m[2] = "host" (group 2)

# preg_match_all with PREG_SET_ORDER
preg_match_all('/(\w+)/', 'a b c', $m, PREG_SET_ORDER);
# $m[0] = ["a", "a"], $m[1] = ["b", "b"], ...

# Named groups
/(?P<name>\w+)/       # PHP/PCRE style (also (?<name>...))
/(?<name>\w+)/        # alternative
\k<name>              # backreference
$matches['name']       # access by name

# Replacement references
$1, $2          # numbered groups
\1, \2          # also valid
${1}            # unambiguous (e.g., ${1}0 to avoid $10)

# Unicode properties (with /u flag)
preg_match('/^\p{L}+$/u', 'Hello')

# PCRE-specific features
(?P<name>...)   # named (Python-compatible)
(?<name>...)    # named (alternative)
(?P=name)       # backreference
(?>...)         # atomic group
a++             # possessive
\R              # any line break sequence
(*FAIL)         # force fail
(*SKIP)         # skip in findall

# Delimiter alternatives (when pattern contains /)
# /pattern/, #pattern#, ~pattern~, |pattern|, !pattern!

Go (RE2)

Go 标准库 regexp 用 RE2 引擎,保证线性时间,无灾难性回溯。代价: 不支持反向引用和回溯特性。语法上 \d 等需写为 [0-9] 或 \d(RE2 支持)。Go 正则适合处理不可信输入,但无法用于需要反向引用的场景。

regex
# Go regex (RE2 engine - linear time, no backreferences)

import "regexp"

# Compile pattern (must use raw string `...` or escape backslashes)
re := regexp.MustCompile(`\bword\b`)
re := regexp.MustCompile("\\bword\\b")  # with escaping

# Methods
re.MatchString("a word here")     # bool
re.FindString("a word here")      # first match or ""
re.FindAllString("a b c", -1)     # []string of all matches
re.FindStringSubmatch("user@host") # [whole, group1, group2, ...]
re.FindAllStringSubmatch(text, -1) # [][]string
re.FindStringIndex("abc")         # [start, end] or nil
re.ReplaceAllString("a-b-c", "+") # "a+b+c"
re.ReplaceAllStringFunc(text, func)
re.Split("a,b,c", -1)             # []string

# -1 means "all matches"; positive N limits splits/matches

# Named groups
re := regexp.MustCompile(`(?P<year>\d{4})-(?P<month>\d{2})`)
m := re.FindStringSubmatch("2024-01")
# m[0]="2024-01", m[1]="2024", m[2]="01"

# Access by name
re.SubexpNames()  # ["", "year", "month"]

# IMPORTANT: RE2 limitations
# - NO backreferences (\1)
# - NO atomic groups (?>...)
# - NO possessive quantifiers (a++)
# - NO lookbehind (?<=...)
# - NO lookahead (?!...) — but (?=...) IS supported
# - NO recursive patterns
# - NO conditional patterns

# In exchange: linear-time guarantee (no catastrophic backtracking)

# Flags (inline only — no separate flags arg)
(?i)           # case-insensitive
(?m)           # multiline
(?s)           # dotall
(?i)pattern    # applies to whole pattern
(?i)abc(?-i)def  # scoped (PCRE-style, not in Go)

# Compile vs MustCompile
re, err := regexp.Compile(pattern)  # returns error
re := regexp.MustCompile(pattern)   # panics on error

# Quote literal string for safe regex use
regexp.QuoteMeta("1.5")  # "1\\.5"

.NET / C#

.NET 的 Regex 功能全面: 命名组 (?<name>...)、(?'name')、反向引用 \k<name>、子表达式 (?<name>...)、平衡组。RegexOptions 控制行为(IgnoreCase、Multiline、Compiled 等)。Compiled 提升速度但增加启动时间。支持变长预查反向。

regex
# .NET regex (System.Text.RegularExpressions)

using System.Text.RegularExpressions;

# Static methods
Regex.IsMatch("abc", "a.c")                 # bool
Regex.Match("abc", "a.c")                   # first match
Regex.Matches("a1 b2", "\d+")              # all matches
Regex.Replace("a-b-c", "-", "+")            # "a+b+c"
Regex.Split("a,b,c", ",")                   # string[]

# Instance (compiled for reuse)
Regex re = new Regex("\\d+", RegexOptions.Compiled);
re.Match("abc 123");
re.Matches("abc 123 456");
re.Replace("abc 123", "X");

# Flags (RegexOptions enum)
RegexOptions.IgnoreCase      # i
RegexOptions.Multiline       # m
RegexOptions.Singleline      # s (dotall - confusing name!)
RegexOptions.ExplicitCapture # only named groups capture
RegexOptions.IgnorePatternWhitespace  # x (extended)
RegexOptions.Compiled        # compile to IL (faster, slower startup)
RegexOptions.CultureInvariant

# Combine with |
new Regex("abc", RegexOptions.IgnoreCase | RegexOptions.Multiline)

# Named groups (.NET style)
(?<name>pattern)       # capture
(?<name1>...) (?<name2>...)  # multiple
\k<name>               # backreference
${name}                # in replacement

# Alternation in group names (balancing groups - .NET specific)
(?<name1-name2>...)    # pop name2 stack into name1

# Group access
Match m = re.Match("2024-01-15");
m.Groups["year"].Value
m.Groups[1].Value

# Replace with MatchEvaluator (function)
Regex.Replace("abc", "\w", m => m.Value.ToUpper())  # "ABC"

# Timeouts (.NET 4.5+)
Regex re = new Regex("a+", RegexOptions.None, TimeSpan.FromSeconds(1));

# Unicode supported by default
Regex.IsMatch("Hello", @"\p{L}+")  # true (note: @"" verbatim string)

# Verbatim strings (@"...") avoid double-escaping
@"\d+"    # same as "\\d+" but cleaner

# Surprisingly powerful: balancing groups for nested matching
# Can match balanced parentheses (unique to .NET)
 @"((?:[^()]+|(?<Open>\()|(?<Close-Open>\)))*)"
20

高级技巧

原子组

(?>...) 原子组: 一旦匹配成功,内部不回溯。可防止灾难性回溯: (?>a+)+b 在无 b 时快速失败。代价: 可能导致本可匹配的失败。PCRE/PHP/Perl/.NET/Java 支持,JS 和 Python 不支持(可用 possessive 替代)。

regex
# Atomic group: (?>...) - once matched, NO backtracking into it
# Supported in PCRE, Java, .NET — NOT in JavaScript or Python's re

# Greedy with backtracking
\d+a           # in "123a", \d+ takes "123", then backtracks to "12" if needed
(?>\d+)a       # atomic: \d+ takes "123" and CANNOT give back

# When atomic helps: prevent catastrophic backtracking
# Dangerous: (a+)+b on "aaaa...!"
# Safe (atomic): (?>a+)+b  or  (?>a+b)

# Use case: prevent unwanted backtracking in alternation
# Without atomic: tries each alternative, backtracks on fail
(?>cat|category)   # if "cat" matches, won't try "category"

# Practical: match HTML tag without backtracking issues
(?><[a-z][a-z0-9]*\b[^>]*>)

# Quoted string without backtracking issues
(?>"[^"]*")

# Java
Pattern p = Pattern.compile("(?>\\d+)a");

# PHP/PCRE
preg_match('/(?>\d+)a/', '123a')

# .NET
new Regex("(?>\d+)a")

# Python: not in re module; use 'regex' module
import regex
regex.search(r"(?>\d+)a", "123a")

# JavaScript: NOT supported
# Use possessive quantifiers (also not in JS) or refactor pattern

占有量词

a++ a*? a{2,5}++ 等占有量词: 贪婪匹配且不回溯。等价原子组 (?>a+)。可提升性能并防止灾难性回溯。PCRE/Java/Perl 支持,JS 和 Python 不支持。Go RE2 不需要(本身不回溯)。

regex
# Possessive: a++ a*+ a?+ a{n,m}+
# Like greedy but NEVER gives back (no backtracking)

# Greedy (gives back if needed)
\d+a    # "123a": \d+ takes "123", matches a
\d+x    # "123x": \d+ takes "123", then x matches

# Possessive (never gives back)
\d++a   # "123a": \d+ takes "123", matches a
\d++x   # "123x": \d+ takes "123", x fails - NO backtrack, FAIL

# When possessive helps: avoid catastrophic backtracking
# Dangerous: (a+)+  on "aaaaaa!"
# Possessive: (a++)+  or  (?>a+)+
# Both prevent exponential backtracking

# Common possessive patterns
"[^"]*"++     # possessive quoted string
<[a-z]++>      # possessive tag name
\d++\.\d++   # possessive decimal

# Java supports possessive quantifiers
Pattern.compile("\\d++a");

# PHP/PCRE supports possessive
preg_match('/\d++a/', '123a')

# .NET: does NOT support possessive quantifiers directly
# Use atomic groups instead: (?>\d+)a

# Python 're': NOT supported
# Python 'regex' module: supports \d++ ? Actually no, use (?>\d+)

# JavaScript: NOT supported
# Use atomic groups (also not in JS) or refactor

# Comparison: possessive vs atomic
\d++       # possessive quantifier (Java, PCRE)
(?>\d+)    # atomic group (Java, PCRE, .NET)
# Both prevent backtracking; possessive is shorter syntax

条件模式

(?(condition)yes|no) 根据条件匹配: (?(1)yes|no) 第 1 组已匹配时用 yes 分支。PCRE/Perl/.NET 支持,JS/Python/Go 不支持。常用于解析复杂结构,但可读性差,多数场景可用分支逻辑替代。

regex
# Conditional: (?(condition)yes-pattern|no-pattern)
# PCRE, .NET, Perl — NOT JavaScript or Python's re

# Conditional based on group match
(?(1)yes|no)    # if group 1 matched, use yes-pattern; else no-pattern
(?(<name>)yes|no)  # named group version

# Example: match "Mr. Smith" or "Smith" based on title presence
(Mr\.\s+)?(\w+)(?(1)\s+\2|)  # if title, require last name repeated?

# Practical: optional separator
# If first separator was -, second must be -
# If first was /, second must be /
(\d{4})([-/]?)(\d{2})(?(2)\2|[-/]?)(\d{2})

# Conditional based on lookahead
(?(?=\d)\d+|[a-z]+)  # if next is digit, match digits; else letters

# Python 'regex' module supports conditionals
import regex
regex.search(r"(?(1)yes|no)", text)

# .NET supports conditionals
new Regex("(?(1)yes|no)")

# PCRE/PHP supports conditionals
preg_match('/(?(1)yes|no)/', $text)

# Java: does NOT support conditionals directly
# Use alternation and lookarounds as workaround

# JavaScript: NOT supported
# Refactor with alternation:
# (?(1)A|B) becomes: (?:group1A|B) in some cases
# Or use two separate patterns and combine in code

# Real-world: parse key-value where value is conditional
# If key is "url", value must be URL; if "count", value must be number
(url|count):(?(?<=url:)https?://\S+|\d+)

递归 (PCRE)

(?R) 或 (?1) 递归匹配整个模式或指定组,可匹配嵌套结构如括号 \((?:[^()]|(?R))*\)。PCRE/Perl/.NET 支持,JS/Python/Go 不支持。递归正则功能强大但难调试,复杂嵌套解析建议用专门解析器。

regex
# Recursive patterns: (?R) or (?N) - match nested structures
# PCRE, Perl, .NET (balancing groups) — NOT JS, Java, Python re

# Match balanced parentheses (PCRE)
\((?:[^()]+|(?R))*\)
# Matches: "(a)", "(a(b)c)", "((a)(b))", etc.

# Named recursion (PCRE)
(?<paren>\((?:[^()]+|(?&paren))*\))

# Match nested HTML/XML tags (PCRE)
(?<tag><([a-z]+)(?:[^>]+)>[^<]*</\2>|<([a-z]+)(?:[^>]+)>(?R)*</\3>)

# Python 'regex' module supports recursion
import regex
regex.search(r"\((?:[^()]+|(?R))*\)", "(a(b)c)")

# .NET: use balancing groups instead of recursion
# Match balanced parens:
\((?:[^()]+|(?<Open>\()|(?<Close-Open>\)))*\)
# More complex but powerful

# PHP/PCRE
preg_match('/\((?:[^()]+|(?R))*\)/', '(a(b)c)')

# JavaScript: NOT supported
# Use a parser or stack-based approach in code
function matchBalanced(s) {
  let depth = 0, start = -1, results = [];
  for (let i = 0; i < s.length; i++) {
    if (s[i] === "(") { if (depth === 0) start = i; depth++; }
    else if (s[i] === ")") {
      depth--;
      if (depth === 0 && start !== -1) {
        results.push(s.slice(start, i + 1));
        start = -1;
      }
    }
  }
  return results;
}

# Java: NOT supported
# Use third-party library or stack-based parser

性能技巧

性能优化: 复用编译后的模式、具体化字符类(避免 .)、使用锚点、用非捕获组、避免灾难性回溯、优先用否定字符类而非懒量词、按概率排序交替、设超时、用 RE2 处理不可信输入、简单操作用字符串方法。

regex
# 1. Compile patterns for reuse (Python, Java)
import re
p = re.compile(r"\d+")  # compile once
for line in lines:
    p.search(line)  # reuse

# 2. Be specific - avoid . when you know the char class
# Bad:  .*
# Good: [a-z]*  or  \w*  or  [^,]*
# Faster and clearer intent

# 3. Anchor when possible
# Bad:  \d+
# Good: ^\d+$  (if validating entire string)
# Anchors let the engine fail fast

# 4. Use non-capturing groups when you don't need captures
# Bad:  (\d{1,3}\.){3}\d{1,3}
# Good: (?:\d{1,3}\.){3}\d{1,3}
# Saves memory and time

# 5. Avoid catastrophic backtracking
# Bad:  (a+)+b  on "aaaa...!"
# Good: a++b (possessive) or (?>a+)b (atomic) or a*b

# 6. Prefer negated classes over lazy quantifiers for delimited content
# Slower: ".*?"
# Faster: "[^"]*"

# 7. Use atomic groups / possessive quantifiers when safe
(?>\d+)  # no backtracking needed for digits

# 8. Order alternation by likelihood
# Bad:  (rare|common)
# Good: (common|rare)
# Engine tries left to right

# 9. Avoid nested quantifiers on overlapping alternatives
# Bad:  (a|a)*b
# Good: a*b

# 10. Set timeouts for untrusted input (Python 3.11+, .NET 4.5+)
re.compile(pattern).match(input, timeout=1.0)

# 11. Use RE2 for untrusted patterns (Go, or libraries)
# Linear time, immune to catastrophic backtracking
# Trade-off: no backreferences

# 12. Benchmark with realistic data
# Use timeit (Python) or performance.now() (JS)

# 13. Avoid lookahead/lookbehind when a simpler pattern works
# Bad:  (?<=\$)\d+
# Good: \$(\d+) and use group 1 (often faster)

# 14. Use String methods for simple operations
# Bad:  text.replace(/^\s+|\s+$/g, "")
# Good: text.trim()  (much faster)

这篇内容对您有帮助吗?

学习路径

从零开始学习

通过结构化课程从头学习这个语言。