入门
基础语法
正则表达式使用特殊字符进行模式匹配。. 匹配任意字符,[] 定义字符类,\d \w \s 是常用字符集的简写。在 [] 内使用 ^ 表示取反。
# 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元字符
元字符具有特殊含义。要字面匹配它们,请加反斜杠前缀。在字符类 [] 内,只有 ] \ ^ 和 - 需要转义(且 - 仅在不处于开头/结尾时需要)。
# 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 模式)可让 . 匹配包括换行符在内的所有字符。点号在字符类 [] 内是字面量,无需转义。
# 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)。
# 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 标志使用。
# 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字符类
字符集合 [abc]
方括号定义字符集合,匹配其中任意一个字符。顺序无关。常见用途:元音 [aeiou]、十六进制 [0-9a-fA-F]。在集合内,大多数元字符变为字面量。
# 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 是常见取反简写。
# 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 范围。
# 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 数字。
# 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*$ 可确保不含数字。
# 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)可实现高级字符类运算。
# 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}量词
星号 *(零次或多次)
* 匹配前一项 0 次或多次。默认贪婪(尽量多匹配)。常见组合: .* 匹配任意字符串(除换行), \s* 匹配可选空白。注意 (a*)* 等嵌套量词可能导致灾难性回溯。
# * 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+ 匹配单词。在重复内容为空时,+ 不会无限循环(而 * 在某些构造中可能)。
# + 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}?)。在分组开头有特殊含义:(?:) 非捕获组、(?=) 正向预查、(?!) 负向预查等。
# ? 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} 间不能有空格。
# 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 等嵌套结构,贪婪常导致过度匹配,应改用懒量词或更具体的字符类。
# 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>' 而非整体。懒量词从起始位置尝试最小匹配,逐步扩展。并非总是更快——取决于输入和模式。
# 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锚点与边界
起始锚点 ^
^ 匹配字符串开始位置(零宽)。默认不匹配行首,需 m 标志才能在每行开头匹配。在字符类开头 [^abc] 表示取反,与锚点含义不同。^ 与 $ 配合做整行/整串验证。
# ^ 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)。用 ^...$ 做整串验证。
# $ 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 防止部分匹配。
# \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'。常用于查找嵌在其他词中的子串。
# \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 标志。
# 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分组与捕获
捕获组 ()
圆括号创建捕获组,记录匹配的子串以便后续引用。exec/match 返回的数组中,索引 0 是整体匹配,1..n 是各捕获组。捕获会影响性能,无需捕获时用 (?:...) 非捕获组。
# 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 中无明确上限但建议尽量少用。
# 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)非捕获组 (?:...)
(?:...) 分组但不捕获,避免占用组编号并提升性能。常用于应用量词到多个字符或限定交替范围。在不需要引用子匹配时,优先使用非捕获组。
# (?:...) 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。
# 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) 限定范围。引擎按顺序尝试各分支,首个匹配胜出(非最长匹配)。
# | 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 不支持。
# \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)非捕获组
基础非捕获
(?:...) 是最常用的非捕获组:仅分组不记录匹配。在不需要后续引用子串时,应优先使用而非普通捕获组。它能提升匹配速度并避免污染组编号。
# (?: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+)? 匹配可选小数部分。
# 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))。外层捕获组记录整体,内层非捕获组仅分组。合理搭配可只捕获需要的部分,提升可读性和性能。注意每层都增加解析开销。
# 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 等引擎根本不支持捕获组,所有分组均为非捕获。
# 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+)?。它是书写清晰正则的基础工具。
# 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预查与预查反向
正向预查 (?=)
(?=...) 要求当前位置后跟指定模式,但不消耗字符(零宽)。\d+(?=px) 匹配 '100px' 中的 '100' 但不包含 'px'。预查内的捕获组仍会记录,但 JS 的 matchAll 行为需注意。
# (?=...) - 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'。常用于排除特定上下文。
# (?!...) - 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 旧版)。
# (?<=...) - 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) 匹配独立单词。
# (?<!...) - 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,}$ 密码需同时含大小写字母和数字。每个预查独立从起始位置检查,互不消耗字符。
# 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 旧版不支持预查反向。
# 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替换与替换
基础替换
JS 的 replace 默认只替换第一个匹配,需 g 标志替换全部。Python 的 re.sub 默认替换全部,用 count 参数限制数量。替换字符串中的 $ 具有特殊含义,需注意转义。
# 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 用 \,二者不能混用。
# 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}。命名组让替换模板更易读,尤其在复杂模式中。命名组必须在模式中先定义,否则报错或无效。
# 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 快。
# 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 对象。
# 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> 等价 $&。理解这些特殊变量能简化许多文本处理任务。
# 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"标志
全局 g
g 标志让匹配从 lastIndex 继续而非每次从开头。test()/exec() 在 g 模式下有状态(lastIndex),重复调用会遍历所有匹配。match() 在 g 模式下返回所有匹配数组(无捕获组详情),用 matchAll() 获取详情。
# 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。
# 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。
# 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 行为。
# 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 标志。
# 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 等库。
# 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常见模式
邮箱
邮箱正则复杂度依需求而定。简单版 ^[^@\s]+@[^@\s]+\.[^@\s]+$ 够用于基本校验。RFC 5322 完整正则极长,实际开发建议用 HTML5 type=email 或专用库,正则仅做粗筛。
# 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),正则易遗漏边界情况。
# 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 等专用库做验证和格式化。
# 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。
# 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() 函数语法,需另行处理。
# 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$。各国邮编规则差异大,需按目标地区定制。
# 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")密码验证
最小长度
^.{8,}$ 要求至少 8 个字符。注意 . 默认不匹配换行,密码一般不含换行可接受。结合 ^ 和 $ 确保整串满足条件。最小长度是密码策略的基础,建议至少 8 位,敏感场景 12+ 位。
# 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 标志。
# 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。
# 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 */ }特殊字符
(?=.*[!@#$%^&*]) 确保含至少一个指定特殊字符。字符集可根据业务需求调整。注意在字符类 [] 内,大多数特殊字符无需转义,但 ] \ ^ - 需注意位置或转义。
# 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,}$ 组合多种要求。每个预查独立从开头检查,互不影响。这种写法清晰易维护,比单一复杂正则更易调整。注意预查顺序不影响结果。
# 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)日期匹配
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 对象或专用库。
# 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 # invalidDD/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 解析。
# 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$。时区考虑更复杂,建议用专用时间库。
# 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 表示东八区。
# 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() 转换并做范围校验(如闰年判断),比纯正则更可靠。
# 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数字匹配
整数
^-?\d+$ 匹配可选负号的整数。^-?\d+$ 包含 -0,若要排除用 ^(0|-?[1-9]\d*)$。\d+ 不匹配千分位逗号,如需支持 1,234 用 ^-?\d{1,3}(,\d{3})*$。注意 \d 在 Unicode 模式下可能匹配全角数字。
# 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+$ 匹配。
# 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}$ 这种格式需根据具体需求调整。
# 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() 接受更宽松的格式。
# 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 格式化和解析。
# 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)$字符串替换
简单替换
str.replace(/foo/g, 'bar') 替换所有 'foo'。无 g 标志只替换第一个。简单字面量替换用 str.replaceAll('foo', 'bar') 更快且无需转义。注意替换字符串中的 $ 有特殊含义,字面 $ 需写为 $$。
# 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。性能上,简单字符串替换优于正则。
# 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 用 \,语法不同。
# 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 不支持。
# 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() 更快更易读,正则用于复杂空白清理。
# 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 风险,不应在安全敏感场景使用。
# 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&T".replace(/&/g, "&")
"<tag>".replace(/</g, "<").replace(/>/g, ">")
" ".replace(/ /g, " ")
""".replace(/"/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.提取与分割
提取第一个匹配
str.match(/pattern/) 返回第一个匹配及捕获组(无 g 标志)。exec() 行为类似。Python 用 re.search() 返回 Match 对象或 None。只需判断是否存在用 test()(JS)或 re.search()(Python)。
# 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() 返回迭代器。
# 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)。
# 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,比正则更健壮。
# 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),正则维护性差。
# 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贪婪与懒量词
贪婪默认
默认贪婪: 尽量多匹配,再回溯。'.*' 匹配 '<a><b>' 整体而非 '<a>'。贪婪量词先尝试最大匹配,失败后逐步回退。在无回溯的简单场景下,贪婪通常更快;在可能过度匹配时,需用懒量词或更精确的字符类。
# 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>'。懒量词从最小匹配开始,逐步扩展。并非总比贪婪快——若目标在末尾,懒量词需多次扩展,反而更慢。
# 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 标签匹配 <[^>]+> 比 <.*?> 更高效且不会跨标签匹配。
# 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+)何时用哪种
选择原则: 目标靠近起始用懒,靠近末尾用贪婪;避免 .* 跨界匹配,优先用否定字符类 [^"]* 匹配引号内内容。性能: [^<]* 比 .*? 快(无回溯)。可读性: 否定字符类比懒量词意图更清晰。
# 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(线性时间,无回溯)。
# 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反向引用
编号反向引用
\1 \2 ... 引用对应编号的捕获组匹配内容。(\w+)\s+\1 匹配重复单词如 'hello hello'。反向引用要求前后内容完全相同。注意 RE2 不支持反向引用,Go 标准库正则也不支持。
# \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+ 均支持,语法略有差异。
# 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 引用的是捕获组匹配的具体内容,不是模式本身。编辑器中常用此模式检测文本中的笔误。
# 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\bHTML 标签匹配
<([a-z][a-z0-9]*)\b[^>]*>[\s\S]*?</\1> 匹配配对标签如 <div>...</div>。\1 确保开闭标签名一致。注意此正则不能处理嵌套标签(需递归正则),生产环境用 DOM 解析器更可靠。
# 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" 这种不配对的情况。捕获组确保了配对。
# 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 productionUnicode 正则
Unicode 模式
JS 的 u 标志启用 Unicode 模式: 正确处理代理对(emoji 等超出 BMP 的字符),. 匹配完整码点。无 u 标志时,emoji 如 𝐀(\uD835\uDC00)被当作两个字符。处理非 ASCII 文本务必加 u 标志。
# 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 标准。
# 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] 更全面,适合多语言文本处理。
# \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}。
# \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 等专用库,正则难以覆盖所有情况。
# 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}语言差异
JavaScript
JS 正则用 /pattern/flags 字面量或 RegExp 构造。ES2018+ 支持预查反向、s 标志、命名组。ES2025 提案支持修饰符。注意 String.match 与 RegExp.exec 行为差异,g 标志下 test/exec 有 lastIndex 状态。
# 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 库支持更多特性(变长预查反向)。
# 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, recursJava
Java 用 Pattern.compile() 和 Matcher。字符串中反斜杠需双重转义 \\d。支持命名组 (?<name>...) 和 \k<name>。Pattern.UNICODE_CHARACTER_CLASS 启用 Unicode 模式(\d 等价 \p{Nd})。注意 Java 正则编译较慢,应复用 Pattern 对象。
# 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,部分语法略有变化。
# 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 正则适合处理不可信输入,但无法用于需要反向引用的场景。
# 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 提升速度但增加启动时间。支持变长预查反向。
# .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>\)))*)"高级技巧
原子组
(?>...) 原子组: 一旦匹配成功,内部不回溯。可防止灾难性回溯: (?>a+)+b 在无 b 时快速失败。代价: 可能导致本可匹配的失败。PCRE/PHP/Perl/.NET/Java 支持,JS 和 Python 不支持(可用 possessive 替代)。
# 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 不需要(本身不回溯)。
# 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 不支持。常用于解析复杂结构,但可读性差,多数场景可用分支逻辑替代。
# 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 不支持。递归正则功能强大但难调试,复杂嵌套解析建议用专门解析器。
# 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 处理不可信输入、简单操作用字符串方法。
# 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)相关 正则表达式 代码片段
Copy-paste ready code for common tasks.
Character Classes
Match sets and ranges of characters.
Quantifiers
Control how many times a token repeats.
Groups and Capturing
Capture, non-capture, and named groups.
Lookaround
Zero-width assertions around a position.
Alternation
Match one of several branches.
Anchors
Match positions instead of characters.
Backreferences
Reuse a previously captured group.
Common Patterns
Practical patterns for everyday validation.
这篇内容对您有帮助吗?