Regex pattern (biểu thức chính quy) là các mẫu tái sử dụng dùng để khớp với các chuỗi ký tự cụ thể trong văn bản - hãy hình dung chúng như một công cụ "tìm kiếm" siêu mạnh, hoạt động trên hầu hết mọi ngôn ngữ lập trình. Dù bạn đang xác thực địa chỉ email, làm sạch số điện thoại từ một form nhập liệu, hay phân tích file log, một vài regex được chọn lọc kỹ sẽ xử lý được 90% những gì bạn cần. Hướng dẫn này tổng hợp các pattern thực dụng nhất, giải thích chi tiết từng pattern và hướng dẫn bạn cách tùy chỉnh chúng.
Mục lục
Ôn Nhanh Cú Pháp Regex
Trước khi đi vào các pattern cụ thể, đây là bảng tóm tắt các thành phần cơ bản mà bạn sẽ thấy lặp đi lặp lại. Dù bạn đã dùng regex trước đây, việc có tất cả trong một chỗ vẫn rất tiện.
| Token | Ý nghĩa | Ví dụ khớp |
|---|---|---|
.
|
Bất kỳ ký tự nào trừ ký tự xuống dòng |
a.c
khớp với
abc
,
a1c
|
\d
|
Bất kỳ chữ số nào (0-9) |
\d\d
khớp với
42
|
\w
|
Ký tự từ (chữ cái, chữ số, dấu gạch dưới) |
\w+
khớp với
hello_world
|
\s
|
Khoảng trắng (space, tab, xuống dòng) |
\s+
khớp với nhiều khoảng trắng liên tiếp
|
^
/
$
|
Đầu / cuối chuỗi |
^\d+$
chỉ khớp với
123
|
{n,m}
|
Lặp từ n đến m lần |
\d{2,4}
khớp từ
12
đến
1234
|
[abc]
|
Lớp ký tự - một trong các ký tự a, b, c |
[aeiou]
khớp với bất kỳ nguyên âm nào
|
(?:...)
|
Nhóm không lưu (non-capturing group) | Nhóm các phần tử mà không lưu backreference |
(?=...)
|
Lookahead dương (positive lookahead) | Kiểm tra điều kiện phía sau mà không tiêu thụ ký tự |
Hướng dẫn regular expressions trên MDN Web Docs là tài liệu tham khảo tốt nhất cho cú pháp regex trong JavaScript, và hầu hết các pattern bên dưới đều có thể dùng trực tiếp trong Python, PHP, Java và Ruby với một vài khác biệt nhỏ về flag.
Xác Thực Email
Email là trường hợp sử dụng regex kinh điển - và cũng là nơi nhiều lập trình viên mắc sai lầm vì cố gắng quá chặt chẽ. Theo
đặc tả RFC 5322
, về mặt kỹ thuật các địa chỉ như
"very unusual"@example.com
là hợp lệ, nhưng hầu như không có regex nào xử lý được. Với 99% trường hợp xác thực đầu vào thực tế, hãy dùng pattern thực dụng sau:
^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$
Ý nghĩa từng phần:
-
[a-zA-Z0-9._%+\-]+- phần local (trước @); cho phép dấu chấm, dấu cộng, dấu gạch ngang, dấu gạch dưới -
@- ký tự @ theo nghĩa đen -
[a-zA-Z0-9.\-]+- tên miền, bao gồm cả subdomain -
\.[a-zA-Z]{2,}- TLD tối thiểu 2 ký tự (.io, .com, .museum)
URL và Địa Chỉ Web
Pattern khớp URL bao gồm mọi thứ từ trích xuất liên kết trong văn bản thuần túy đến xác thực trường website do người dùng nhập.
https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_+.~#?&\/=]*)
-
https?- khớp cảhttpvàhttps -
(?:www\.)?- tiền tố www tùy chọn -
[-a-zA-Z0-9@:%._+~#=]{1,256}- các ký tự hostname, tối đa 256 ký tự -
\.[a-zA-Z0-9()]{1,6}- TLD -
\b(?:[-a-zA-Z0-9()@:%_+.~#?&\/=]*)- path, query string và fragment tùy chọn
Nếu bạn chỉ cần xác thực (không trích xuất), hãy bọc pattern bằng anchor
^
và
$
.
Số Điện Thoại
Số điện thoại nổi tiếng là lộn xộn vì định dạng thay đổi rất nhiều theo từng quốc gia và thói quen người dùng. Hai pattern sau đây bao phủ hầu hết các tình huống:
Định dạng Mỹ/Canada (NANP)
^(\+1[-.\s]?)?(\(?\d{3}\)?[-.\s]?)?\d{3}[-.\s]?\d{4}$
Khớp với:
555-867-5309
,
(555) 867 5309
,
+1.555.867.5309
,
5558675309
Quốc tế (định dạng E.164)
^\+[1-9]\d{6,14}$
E.164 là định dạng được hầu hết các API điện thoại sử dụng (Twilio, AWS SNS). Nó bắt đầu bằng
+
và mã quốc gia, không có khoảng trắng hay dấu câu.
Ngày và Giờ
Pattern khớp ngày tháng rất phổ biến trong các trình phân tích log, bộ xác thực form và data pipeline. Định dạng bạn cần nhắm tới phụ thuộc vào nguồn dữ liệu đầu vào.
ISO 8601 (YYYY-MM-DD)
^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$
Định dạng Mỹ (MM/DD/YYYY)
^(0[1-9]|1[0-2])\/(0[1-9]|[12]\d|3[01])\/\d{4}$
Giờ 24h (HH:MM hoặc HH:MM:SS)
^([01]\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?$
Lưu ý rằng các pattern này chỉ xác thực định dạng, không kiểm tra tính hợp lệ của lịch. Chúng vẫn sẽ chấp nhận
2024-02-31
(ngày 31 tháng 2 không tồn tại). Để xác thực ngày tháng chặt chẽ, hãy phân tích bằng thư viện ngày tháng của ngôn ngữ bạn đang dùng sau khi kiểm tra regex.
Xác Thực Độ Mạnh Mật Khẩu
Các quy tắc mật khẩu thường yêu cầu kết hợp nhiều loại ký tự và độ dài tối thiểu. Lookahead giúp xử lý điều này gọn gàng mà không cần nhiều lần kiểm tra riêng lẻ.
Tối thiểu 8 ký tự, ít nhất một chữ hoa, một chữ thường, một chữ số
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$
Mạnh: 8+ ký tự, chữ hoa, chữ thường, chữ số và ký tự đặc biệt
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]).{8,}$
Mỗi
(?=.*[...])
là một lookahead quét toàn bộ chuỗi để tìm ít nhất một ký tự khớp. Phần cuối
.{8,}
đảm bảo độ dài tối thiểu. Bạn có thể đổi
{8,}
thành
{12,}
để yêu cầu tối thiểu 12 ký tự, phù hợp với
hướng dẫn NIST SP 800-63B
.
Địa Chỉ IP
IPv4
^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$
Pattern này từ chối đúng các giá trị như
999.0.0.1
bằng cách khớp từng octet theo dải 0-255 một cách tường minh.
IPv6 (đơn giản hóa)
^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$
Pattern này xử lý định dạng đầy đủ 8 nhóm. Đối với ký hiệu rút gọn (ví dụ
::1
cho loopback), pattern sẽ phức tạp hơn đáng kể - lúc đó, dùng thư viện mạng để phân tích sẽ đáng tin cậy hơn regex.
HTML và Markup
Một vài pattern có mục tiêu rõ ràng thực sự hữu ích ở đây. Lời khuyên chung "đừng phân tích HTML bằng regex" vẫn đúng với toàn bộ tài liệu - hãy dùng DOM parser phù hợp như BeautifulSoup hoặc DOMParser cho việc đó. Nhưng với các tác vụ cụ thể, giới hạn rõ ràng, regex hoạt động tốt.
Xóa tất cả thẻ HTML
<[^>]*>
Trích xuất nội dung từ một thẻ cụ thể (ví dụ: <title>)
([^<]*)<\/title>
Capture group 1 chứa nội dung văn bản của thẻ title.
Khớp mã màu hex trong HTML
#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})\b
Khớp cả dạng rút gọn 3 chữ số (
#fff
) và dạng đầy đủ 6 chữ số (
#ffffff
).
Các Pattern Tiện Ích Thường Dùng
Những pattern này xuất hiện liên tục trong nhiều loại dự án khác nhau.
Slug (chuỗi thân thiện với URL)
^[a-z0-9]+(?:-[a-z0-9]+)*$
Khớp các chuỗi như
my-blog-post-2024
. Không có chữ hoa, không có dấu gạch ngang ở đầu/cuối, không có dấu gạch ngang đôi.
Số thẻ tín dụng (định dạng cơ bản, không có khoảng trắng)
^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|6(?:011|5[0-9]{2})[0-9]{12})$
-
Bắt đầu bằng
4- Visa (13 hoặc 16 chữ số) -
Bắt đầu bằng
51-55- Mastercard (16 chữ số) -
Bắt đầu bằng
34hoặc37- Amex (15 chữ số) -
Bắt đầu bằng
6011hoặc65- Discover (16 chữ số)
Chuẩn hóa khoảng trắng (gộp nhiều khoảng trắng thành một)
\s{2,}
Thay thế các kết quả khớp bằng một khoảng trắng duy nhất để làm sạch dữ liệu đầu vào lộn xộn của người dùng hoặc văn bản được scrape.
Chỉ chữ số
^\d+$
Chỉ ký tự chữ và số
^[a-zA-Z0-9]+$
Khớp dòng chứa một từ (không phân biệt hoa thường với flag)
^.*\bword\b.*$
Ranh giới từ
\b
ngăn việc khớp
word
bên trong
password
.
Trích xuất số phiên bản (semver)
\bv?(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9.]+))?(?:\+([a-zA-Z0-9.]+))?\b
Lấy ra phần major, minor, patch, nhãn pre-release và build metadata từ các chuỗi như
v2.14.0-beta.1+build.42
.
Flag và Các Mẹo Thực Tế
Các regex pattern hoạt động khác nhau tùy thuộc vào flag bạn áp dụng. Dưới đây là những flag thường cần dùng nhất:
| Flag | JS | Python | Tác dụng |
|---|---|---|---|
| Không phân biệt hoa thường |
i
|
re.IGNORECASE
|
Coi chữ hoa và chữ thường là như nhau |
| Global (tìm tất cả) |
g
|
re.findall()
|
Trả về tất cả kết quả khớp, không chỉ kết quả đầu tiên |
| Multiline |
m
|
re.MULTILINE
|
^
và
$
khớp với ranh giới dòng, không phải ranh giới chuỗi
|
| Dotall |
s
|
re.DOTALL
|
.
cũng khớp với ký tự xuống dòng
|
Một vài thói quen giúp bạn tiết kiệm thời gian debug:
- Luôn kiểm tra với các trường hợp biên - chuỗi rỗng, độ dài tối đa, ký tự Unicode và các chuỗi gần hợp lệ nhưng không hợp lệ.
-
Dùng non-capturing group
(?:...)khi bạn không cần nội dung đã khớp - nhanh hơn và gọn hơn so với capturing group. -
Thêm anchor vào các pattern xác thực
bằng
^và$để tránh trường hợp một chuỗi con hợp lệ nằm bên trong một chuỗi không hợp lệ vẫn lọt qua. -
Cẩn thận với catastrophic backtracking
- các quantifier lồng nhau như
(a+)+có thể khiến regex engine bị treo với đầu vào được tạo đặc biệt. Hãy giữ quantifier đơn giản và cụ thể. - Dùng công cụ kiểm tra regex khi xây dựng pattern. regex101.com hiển thị kết quả khớp trực tiếp, giải thích từng token và cho phép bạn chuyển đổi giữa PCRE, JavaScript, Python và các flavor khác.
Kiểm tra và xác thực regex pattern không còn phải đoán mò
Xây dựng regex pattern đáng tin cậy cho việc xác thực đầu vào sẽ nhanh hơn khi bạn có đúng công cụ trong tay. Khám phá các tiện ích miễn phí dành cho lập trình viên của chúng tôi để làm sạch, kiểm tra và chuyển đổi văn bản bằng regex pattern và nhiều hơn nữa.
Dùng Thử Công Cụ Miễn Phí →
Quantifier greedy (như
.*
) khớp nhiều nhất có thể rồi mới backtrack. Quantifier lazy (như
.*?
) khớp ít nhất có thể. Ví dụ, với chuỗi
bold
, pattern
<.*>
khớp toàn bộ chuỗi, trong khi
<.*?>
chỉ khớp
. Hãy dùng quantifier lazy khi trích xuất nội dung giữa các dấu phân cách.
Hầu hết là có, nhưng vẫn có sự khác biệt. Module
re
của Python dùng cú pháp kiểu PCRE và hỗ trợ named group với
(?P
. JavaScript dùng cú pháp flag hơi khác và không hỗ trợ lookbehind trên các engine cũ (trước ES2018). Khi làm việc đa ngôn ngữ, hãy bám vào tập hợp chung: lớp ký tự, quantifier, anchor và các nhóm cơ bản.
Regex hoàn toàn ổn để xác thực định dạng trong production - nó được dùng trong hầu hết mọi web framework. Rủi ro đến từ các pattern được viết kém có thể bị khai thác bởi tấn công ReDoS (regex denial-of-service) thông qua catastrophic backtracking. Hãy tránh quantifier lồng nhau, giữ pattern cụ thể và luôn đặt giới hạn độ dài đầu vào hợp lý trước khi regex thậm chí chạy.
Trong hầu hết các engine phổ biến, chúng tương đương nhau với đầu vào ASCII. Sự khác biệt xuất hiện với Unicode:
\d
trong một số engine (như Python 3 với chế độ Unicode) khớp với chữ số từ các hệ thống chữ viết khác, chẳng hạn chữ số Ả Rập-Ấn Độ. Nếu bạn chỉ muốn chữ số ASCII 0-9, hãy dùng
[0-9]
để rõ ràng hơn. Với hầu hết xác thực form web, sự khác biệt này không quan trọng.
Bạn cần hai thứ: flag
dotall
(để
.
khớp với ký tự xuống dòng) và có thể cần flag
multiline
(để
^
và
$
neo vào từng dòng thay vì toàn bộ chuỗi). Trong JavaScript, dùng
/pattern/ms
. Trong Python, kết hợp
re.DOTALL | re.MULTILINE
. Nếu không có dotall,
.
sẽ dừng tại các dấu xuống dòng và pattern của bạn sẽ không trải dài qua nhiều dòng.