“Some people, when confronted with a problem, think ‘I know, I’ll use regular expressions.’ Now they have two problems.” - Jamie Zawinski
Chào mừng các bạn đến với thế giới kỳ diệu của Regular Expressions (Regex) - nơi mà một dòng code có thể khiến bạn cảm thấy như thiên tài trong 5 phút, rồi sau đó muốn đập đầu vào tường trong 5 tiếng tiếp theo.
Regex Là Gì? Tại Sao Chúng Ta Cần Nó?
Regular Expression, hay gọi tắt là Regex (đọc là “REG-ex”, không phải “re-GEKS” như một số bạn vẫn đọc), là một chuỗi ký tự đặc biệt được sử dụng để tìm kiếm và xử lý văn bản. Nó giống như một chiếc dao Swiss Army - vô cùng hữu ích nhưng cũng rất dễ tự cắn tay mình.
Tại sao chúng ta cần Regex? Bởi vì đôi khi bạn cần tìm tất cả email trong một đống text, hoặc validate số điện thoại, hoặc đơn giản là muốn khoe với đồng nghiệp rằng bạn có thể viết được những dòng code trông như mật mã CIA.
Cú Pháp Cơ Bản - Bảng Chữ Cái Của Quỷ Dữ
Ký Tự Literal (Literal Characters)
Đây là phần dễ nhất. Nếu bạn muốn tìm chữ “hello”, bạn chỉ cần viết:
Easy peasy, lemon squeezy! Nhưng đừng vội mừng, chúng ta mới chỉ bắt đầu thôi.
Đây là lúc mọi thứ bắt đầu trở nên… thú vị:
.
- Match bất kỳ ký tự nào (trừ newline). Nó như một wildcard trong game bài.*
- Match 0 hoặc nhiều ký tự trước đó. Giống như người yêu cũ, có thể có hoặc không.+
- Match 1 hoặc nhiều ký tự trước đó. Ít nhất phải có một cái.?
- Match 0 hoặc 1 ký tự trước đó. Có hay không cũng được.^
- Bắt đầu của chuỗi. Như lời mở đầu trong một bài thuyết trình.$
- Kết thúc của chuỗi. Như lời kết trong một bài thuyết trình.
Character Classes - Hội Những Ký Tự Đặc Biệt
1
2
3
4
5
| [abc] # Match a, b, hoặc c
[a-z] # Match bất kỳ chữ thường nào
[A-Z] # Match bất kỳ chữ hoa nào
[0-9] # Match bất kỳ số nào
[^abc] # Match bất kỳ ký tự nào NGOẠI TRỪ a, b, c
|
Predefined Character Classes - Những Shortcut Cứu Mạng
1
2
3
4
5
6
| \d # Digit [0-9] - Số
\w # Word character [a-zA-Z0-9_] - Ký tự từ
\s # Whitespace - Khoảng trắng
\D # Không phải số
\W # Không phải ký tự từ
\S # Không phải khoảng trắng
|
Quantifiers - Những Kẻ Đếm Số
Quantifiers cho phép bạn chỉ định bao nhiều lần một pattern xuất hiện:
1
2
3
| {n} # Chính xác n lần
{n,} # Ít nhất n lần
{n,m} # Từ n đến m lần
|
Ví dụ:
1
2
3
| \d{3} # Chính xác 3 số (như 123)
\d{3,5} # Từ 3 đến 5 số (như 123, 1234, hoặc 12345)
\d{3,} # Ít nhất 3 số (như 123, 1234, 12345, ...)
|
Groups và Capturing - Nghệ Thuật Bắt Bằng Chứng
Parentheses ()
tạo ra groups, cho phép bạn:
- Nhóm các phần của pattern lại với nhau
- Capture (bắt) nội dung để sử dụng sau này
1
2
| (abc)+ # Match "abc", "abcabc", "abcabcabc", ...
(\d{3})-(\d{3})-(\d{4}) # Match số điện thoại US format và capture các phần
|
Non-capturing Groups
Đôi khi bạn muốn nhóm nhưng không muốn capture (giống như muốn ăn bánh nhưng không muốn béo):
1
| (?:abc)+ # Nhóm "abc" nhưng không capture
|
Alternation - Sự Lựa Chọn
Dấu |
hoạt động như “OR”:
1
2
| cat|dog # Match "cat" hoặc "dog"
(jpg|png|gif) # Match các định dạng ảnh
|
Lookahead và Lookbehind - Nhìn Trước Nhìn Sau
Đây là phần high-level, dành cho những ai muốn flexing:
1
2
3
4
| (?=...) # Positive lookahead - Có cái gì đó phía sau
(?!...) # Negative lookahead - Không có cái gì đó phía sau
(?<=...) # Positive lookbehind - Có cái gì đó phía trước
(?<!...) # Negative lookbehind - Không có cài gì đó phía trước
|
Những Pattern Thực Tế - Từ Lý Thuyết Đến Thực Hành
Validate Email (Cơ Bản)
1
| ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
|
Lưu ý: Đây chỉ là version cơ bản. Email validation thực sự phức tạp hơn nhiều và có thể khiến bạn mất ngủ.
Số Điện Thoại Việt Nam
1
| ^(0|\+84)(3|5|7|8|9)\d{8}$
|
Tìm URL
1
| https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)
|
Validate Mật Khẩu Mạnh
1
| ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$
|
Regex này yêu cầu:
- Ít nhất 1 chữ thường
- Ít nhất 1 chữ hoa
- Ít nhất 1 số
- Ít nhất 1 ký tự đặc biệt
- Tối thiểu 8 ký tự
Flags - Những Modifier Hữu Ích
Flags thay đổi cách regex hoạt động:
i
- Case insensitive (không phân biệt hoa thường)g
- Global (tìm tất cả matches, không chỉ cái đầu tiên)m
- Multiline (^ và $ match đầu/cuối mỗi dòng)s
- Dotall (. match cả newline)
Ví dụ trong JavaScript:
1
| const regex = /hello/gi; // Global và case insensitive
|
Debugging Regex - Khi Mọi Thứ Đổ Vỡ
Regex debugging là một nghệ thuật. Dưới đây là một số tips:
Break It Down
Đừng viết một regex dài 50 ký tự trong một lần. Bắt đầu đơn giản rồi từ từ thêm vào:
1
2
3
4
5
6
7
8
9
10
| # Bước 1: Match email cơ bản
\w+@\w+
# Bước 2: Thêm domain extension
\w+@\w+\.\w+
# Bước 3: Cho phép dấu chấm trong username
[\w.]+@\w+\.\w+
# Tiếp tục...
|
Trong một số ngôn ngữ, bạn có thể comment regex:
1
2
3
4
5
6
7
8
| (?x) # Enable verbose mode
^ # Start of string
[\w._%+-]+ # Username part
@ # At symbol
[\w.-]+ # Domain name
\. # Dot
[A-Z]{2,} # Top level domain
$ # End of string
|
Regex có thể rất chậm nếu không được viết cẩn thận:
Catastrophic Backtracking
Tránh những pattern như này:
1
| (a+)+b # ĐỪng làm thế này!
|
Sử dụng Non-greedy Quantifiers
1
2
| .*? # Non-greedy, dừng sớm nhất có thể
.* # Greedy, match càng nhiều càng tốt
|
Anchor Your Patterns
1
2
| ^pattern$ # Tốt
pattern # Không tốt
|
Những Lỗi Thường Gặp - Hall of Shame
1
2
3
4
5
| # Sai
3.14
# Đúng
3\.14
|
Không Sử Dụng Raw Strings (trong Python)
1
2
3
4
5
| # Sai
pattern = "\d+"
# Đúng
pattern = r"\d+"
|
Nghĩ Regex Là Silver Bullet
Không phải mọi thứ đều cần regex. Đôi khi string.contains()
đã đủ rồi.
Regex Trong Các Ngôn Ngữ Khác Nhau
JavaScript
1
2
3
| const pattern = /\d+/g;
const text = "I have 5 apples and 3 oranges";
const numbers = text.match(pattern); // ['5', '3']
|
Python
1
2
3
4
5
| import re
pattern = r'\d+'
text = "I have 5 apples and 3 oranges"
numbers = re.findall(pattern, text) # ['5', '3']
|
Java
1
2
3
4
5
| Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher("I have 5 apples and 3 oranges");
while (matcher.find()) {
System.out.println(matcher.group());
}
|
PHP
1
2
3
4
| $pattern = '/\d+/';
$text = "I have 5 apples and 3 oranges";
preg_match_all($pattern, $text, $matches);
print_r($matches[0]); // Array(['5', '3'])
|
Best Practices - Làm Sao Để Không Bị Đồng Nghiệp Ghét
Tên Biến Có Ý Nghĩa
1
2
3
4
5
| // Tệ
const p = /\d{3}-\d{3}-\d{4}/;
// Tốt
const phoneNumberPattern = /\d{3}-\d{3}-\d{4}/;
|
1
2
| const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
// Matches standard email format: username@domain.tld
|
1
2
3
4
| if (typeof input !== 'string' || input.length === 0) {
return false;
}
return emailPattern.test(input);
|
1
2
3
4
5
6
7
8
9
10
| # Tệ - compile mỗi lần
for item in items:
if re.match(r'\d+', item):
# do something
# Tốt - compile một lần
pattern = re.compile(r'\d+')
for item in items:
if pattern.match(item):
# do something
|
Kết Luận
Regex là một công cụ mạnh mẽ, nhưng hãy nhớ:
- Đơn giản là tốt nhất - Nếu có thể làm bằng string methods thông thường, hãy làm vậy
- Có thể đọc được quan trọng hơn ngắn gọn - Đồng nghiệp tương lai (bao gồm chính bạn) sẽ cảm ơn
- Test thoroughly - Regex có thể có những edge cases bất ngờ
- Biết khi nào nên dừng - Đừng cố gắng giải quyết mọi thứ bằng regex
P.S: Nếu bạn có thể đọc hiểu regex này ^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9])(?!.*\s).{8,15}$
mà không cần tra Google, thì chúc mừng - bạn đã officially trở thành regex ninja!