Kiến thức cơ bản về biểu thức chính quy
Biểu thức chính quy (regex) là một mẫu mô tả văn bản. Hướng dẫn này trình bày các thành phần bạn sẽ dùng thường xuyên nhất và những lỗi thường gặp khiến việc so khớp chậm hoặc cho kết quả bất ngờ.
Ký tự thường và lớp ký tự
Hầu hết ký tự khớp với chính nó: mẫu cat tìm văn bản "cat". Một số ký tự có ý nghĩa đặc biệt, như . * + ? ( ) [ ] { } ^ $ | \. Để khớp đúng nghĩa đen một trong các ký tự này, hãy thoát nó bằng dấu gạch chéo ngược, ví dụ \. cho dấu chấm.
Lớp ký tự khớp với một ký tự trong một tập hợp. [abc] khớp a, b hoặc c; [a-z] khớp một chữ cái ASCII viết thường; [^0-9] khớp bất kỳ ký tự nào không phải chữ số. Các dạng viết tắt thường gặp là \d (chữ số), \w (chữ cái, chữ số hoặc dấu gạch dưới), \s (khoảng trắng) và các dạng ngược lại \D, \W và \S. Dấu chấm . khớp với mọi ký tự trừ ký tự xuống dòng.
Trong JavaScript, \w và \d chỉ bao gồm ký tự ASCII. Để khớp chữ cái của bất kỳ ngôn ngữ nào, hãy dùng thuộc tính Unicode như \p{L} cùng cờ u.
Lượng từ và neo
Lượng từ cho biết phần tử đứng trước được lặp lại bao nhiêu lần:
| Cú pháp | Ý nghĩa | Ví dụ |
|---|---|---|
* | 0 lần trở lên | ab* khớp a, ab, abbb |
+ | 1 lần trở lên | \d+ khớp 7 hoặc 2026 |
? | 0 hoặc 1 lần | colou?r khớp color và colour |
{n,m} | Từ n đến m lần | \d{2,4} khớp từ 2 đến 4 chữ số |
Neo khớp với một vị trí thay vì một ký tự. ^ là đầu và $ là cuối của dữ liệu đầu vào (hoặc của mỗi dòng khi có cờ m). \b là ranh giới từ, nên \bcat\b tìm thấy "cat" nhưng không khớp trong "concatenate". Khi kiểm tra toàn bộ một giá trị, hãy neo cả hai đầu: ^\d{5}$ chỉ chấp nhận đúng năm chữ số và không gì khác.
Nhóm và nhóm bắt
Dấu ngoặc đơn nhóm một phần của mẫu để lượng từ hoặc phép chọn áp dụng cho phần đó: (ab)+ lặp lại "ab", còn (jpg|png) khớp một trong hai từ. Một nhóm thông thường cũng bắt văn bản đã khớp, và bạn có thể dùng lại nó khi thay thế dưới dạng $1, $2, v.v.
Nhóm có tên như (?<year>\d{4}) giúp mẫu dễ đọc hơn và có thể được tham chiếu bằng $<year>. Nếu chỉ cần nhóm, nhóm không bắt (?:...) tránh tạo ra nhóm bắt không dùng đến. Công cụ Kiểm tra RegEx của LeedyBox liệt kê từng phần khớp cùng các nhóm bắt và xem trước kết quả thay thế.
Cờ
Cờ thay đổi cách hoạt động của toàn bộ mẫu. Các cờ JavaScript thường dùng là:
g(global): tìm mọi phần khớp thay vì chỉ phần đầu tiên.i(ignore case): coi chữ hoa và chữ thường là như nhau.m(multiline):^và$khớp tại mỗi chỗ xuống dòng.s(dotAll):.khớp cả ký tự xuống dòng.u(unicode): bật so khớp theo Unicode và các lớp\p{...}.
JavaScript còn có y (sticky) và d (vị trí phần khớp), và công cụ kiểm tra cũng hỗ trợ hai cờ này.
Tham lam và lười, và quay lui thảm họa
Theo mặc định, lượng từ là tham lam: chúng khớp nhiều nhất có thể rồi trả lại ký tự nếu phần còn lại của mẫu không khớp. Với văn bản <b>bold</b>, mẫu <.+> khớp toàn bộ chuỗi. Thêm ? làm lượng từ trở thành lười, nên <.+?> chỉ khớp <b>. Một lớp phủ định như <[^>]+> thường rõ ràng và nhanh hơn.
Hầu hết engine regex, kể cả engine của JavaScript, dùng cơ chế quay lui. Lượng từ lồng nhau như (a+)+$ hoặc các lựa chọn chồng lấn có thể khiến engine thử một số lượng tổ hợp khổng lồ khi không khớp, làm treo trang hoặc máy chủ. Hiện tượng này gọi là quay lui thảm họa, và với dữ liệu đầu vào không đáng tin cậy, nó có thể bị lợi dụng cho tấn công từ chối dịch vụ (ReDoS). Hãy tránh lồng lượng từ trên cùng một nhóm ký tự, ưu tiên lớp ký tự cụ thể thay vì .*, và thử mẫu với dữ liệu dài không khớp.
Các phiên bản regex khác nhau
Không có một chuẩn regex duy nhất. JavaScript, Python, Java, .NET, PCRE (được PHP sử dụng), Go và các cơ sở dữ liệu hỗ trợ cú pháp hơi khác nhau. Lookbehind, lượng từ sở hữu (possessive), cú pháp nhóm có tên, lớp Unicode và cú pháp thay thế đều có thể khác. Ví dụ, engine RE2 của Go không hỗ trợ lookaround hay tham chiếu ngược.
Công cụ Kiểm tra RegEx của LeedyBox dùng engine JavaScript của trình duyệt. Một mẫu chạy được ở đó có thể cần chỉnh sửa trong ngôn ngữ khác, vì vậy hãy thử lại ở nơi mẫu sẽ chạy.
Câu hỏi thường gặp
Vì sao mẫu của tôi chỉ khớp lần xuất hiện đầu tiên?
Không có cờ g, JavaScript dừng lại sau phần khớp đầu tiên. Hãy bật g để tìm mọi phần khớp.
Regex có thể kiểm tra địa chỉ email không?
Regex có thể kiểm tra một giá trị trông có hợp lý không, nhưng không thể xác nhận địa chỉ đó có tồn tại. Gửi email xác nhận là cách thông thường để xác minh.
Vì sao ^ chỉ khớp một lần trong văn bản nhiều dòng?
Theo mặc định, ^ và $ chỉ toàn bộ dữ liệu đầu vào. Hãy bật cờ m để chúng khớp ở đầu và cuối mỗi dòng.