Regular Expression là nỗi sợ hãi của nhiều lập trình viên. Những cái gì khó nhớ, ít làm, lập trình viên sẽ Google, Stack Overflow cho nhanh. Chạy được là ok còn không cần hiểu và nhớ kỹ làm gì cho mệt. Lầu dần, ai cũng dùng Regular Expression nhưng chả có mấy ai hiểu quy tắc viết Regular Expression. Trong bài này tôi chia sẻ mấy quy tắc để hiểu và nhớ Regular Expression
1. Regular Expression là gì?
Regular Expression là kỹ thuật viết biểu thức để tìm kiếm chuỗi trùng hợp theo yêu cầu. Regular = thường xuyên, Expression = biểu thức. Chúng ta tạm gọi nó là biểu thức thường xuyên hoặc vài nơi gọi là biểu thức chính quy. Còn tôi đơn giản gọi là regex. Mọi ngôn ngữ lập trình đều hỗ trợ regex. Ứng dụng của regex:
- Kiểm tra tính hợp lệ dữ liệu: email, số điện thoại...
- Tìm những chuỗi phù hợp trong một chuỗi lớn theo biểu thức. Thay vì so sánh từng ký tự mà sử dụng biểu thức, đó mới là sức mạnh của regex
Để học nhanh Regex hiệu quả, thực dụng tôi xin giới thiệu một số nguồn:
- Regex Awesome : link các nguồn, tài liệu học regex
- Regex101 : công cụ trực tuyến để viết và thử regex
- Học Regex thật đơn giản
- Cheatsheet Regex
- Top 15 Commonly Used Regex
Một biểu thức regex đầy đủ sẽ gồm hai phần:
- Phần biểu thức (expression): chúng ta cần phải viết
- Phần cờ điều khiển cách tìm kiếm chuỗi phù hợp (flags): chỉ cần chọn cho phù hợp
2. Giải thích
2.0 Flags - cờ điều khiển
Hãy thử ví dụ này
/^Chào thế giới/gmui
https://regex101.com/r/8ZC0sc/1
Giải thích:
- g: global
- m: multiline
- u: unicode
- i: case insensitive
2.1 Matching Symbol - biểu tượng để khớp
Biểu thức | Giải thích |
---|---|
. | Khớp với mọi ký tự |
^hello | Khớp chuỗi ở đầu dòng |
hello$ | Khớp chuỗi ở cuối dòng |
XY | X tiếp sau đó phải Y |
[abc] | Khớp 1 ký tự hoặc a hoặc b hoặc c. [] định nghĩa một dải các lựa chọn |
[abc][xy] | Ký tự đầu hoặc a hoặc b hoặc c, nối tiếp theo là ký tự x hoặc y |
\bhello\b | Danh giới của bên trái và phải. Chuỗi khớp phải là một từ chứ không phải là chuỗi con |
[^abc] | ^ phủ định dải tiếp theo. Là bất kỳ ký tự nào không phải a hoặc b hoặc |
(color|colour) | Hoặc color hoặc colour . () nhóm các ký tự thành một khối |
countr(y|ies) | Hoặc country hoặc countries |
2.2 Meta character - Ký tự đại diện
Ký tự | Giải thích |
---|---|
\d | Bất kỳ chữ số nào [0-9] |
\D | Ký tự không phải chữ số [^0-9] |
\s | Ký tự cách white space |
\w | Ký tự [a-zA-Z_0-9] |
\W | Ký tự khác, không phải \w |
2.3 Quantifier - đếm số lần khớp
Biểu tượng | Giải thích |
---|---|
* | Xuất hiện 0 đến nhiều lần. Tương đương {0,} |
+ | Xuất hiện 1 đến nhiều lần. Tương đương {1,} |
? | Xuất hiện 0 hoặc 1 lần. Tương đương {0,1} |
{X} | Xuất hiện X lần |
{X,Y} | Xuất hiện từ X đến Y lần |
2.4 Range []
và Group ()
Range []
tạo ra một dải các lựa chọn. Ví dụ:
[nl]
: hoặcn
hoặcl
[a-z]
một ký tự bất kỳ nằm trong dảia
đếnz
[a-z|A-Z]
cũng không khác gì so với[a-zA-Z]
. Việc thêm ký tự OR|
trong[]
không có tác dụng gì
Group ()
nhóm các ký tự bên trong thành một khối.
(png|jpeg|gif|jpg)
: nhóm ký tự tạo thành file extension. Dùng thêm|
để tạo ra các lựa chọn nối bằng OR()
có thể chứa[]
và nhiều ký tự, biểu tượng bên trong.
3.Thực hành theo tình huống
Hãy sử dụng Regex101 để viết regex và xem kỹ giải thích. Ở đây tôi dùng từ khớp
có nghĩa là match
. Chuỗi khớp
có nghĩa là match string
.
Trong các biểu thức regex dưới đây, phần cuối cùng /gmi
chính là cờ điều khiển.
3.1 Cần khớp bất kỳ chuỗi chứa country
hay countries
/\bcountr(y|ies)\b/gmi
https://regex101.com/r/iKoIaX/1
Giải thích:
/
bắt đầu biểu thức/gmi
cờ điều khiển: g (global search) tìm tất cả chuỗi phù hợp, m (multilines) tìm trên nhiều dòng, i (case insensitive) không quan tâm chữ hoa chứ thường(y|ies)
: khớp ký tựy
hoặc chuỗiies
3.2 Số di động ở Việt nam gồm 10 đến 11 chữ số
Yêu cầu:
- Có từ 10-11 chữ số
- Phải có chữ số 0 đầu tiên
/\b0[0-9]{9,10}\b/gmi
https://regex101.com/r/YjmkFm/1
Giải thích:
\b
: word boundry yêu cầu chuỗi khớp phải là một word đầy đủ chứ không phải chuỗi con. Chúng ta phải chặn hai đầu chuỗi di động bằng\b
0
: chữ số đầu tiên buộc phải là số 0[0-9]
: một ký tự nằm trong danh sách từ0
đến9
{9,10}
: số lượng chữ số từ 9 đến 10 ký tự
3.3 Kiểm tra email có hợp lệ
(?:[a-z0-9!#$%&'*+\=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])
Đây là regex tôi lấy nguyên từ Stack Overflow mà không chút nghĩ ngợi. Link chạy thử đây https://regex101.com/r/rtuOuV/1
3.4 Hà nội, Hà lội, ha noi
Cần tìm tất cả các từ Hà nội, Hà lội, ha noi, Ha noi, ha Noi...
/\b(h[aà] [nl][oộô|]i)\b/gmi
https://regex101.com/r/WRIEeX/1
Giải thích:
- Luôn phải chặn hai đầu biểu thức bằng
\b
để yêu cầu chuỗi khớp phải là một từ đầy đủ chứ không phải chuỗi con - Dùng range
[aà]
,[oộô]
để chấp nhận ký tự có dấu và không dấu - Dùng range
[nl]
để chấp nhận khi có người nói ngọn nhầm lẫn giữan
vớil
3.5 FirstName:xxx,LastName:yyyy
Cần khớp chuỗi có dạng FirstName:xxx
,LastName:yyy
. Giữa FirstName: và xxx
có 0 hoặc nhiều khoảng trắng.
Giữa LastName: và yyy
có 0 hoặc nhiều khoảng trắng.
/\bFirstName:[\s]*(\w+),[\s]*LastName:[\s]*(\w+)\b/gm
https://regex101.com/r/arUAJw/1
Giải thích:
\b
: bắt đầu một từ[\s]*
: có 0 hoặc nhiều khoảng trắng(\w+)
: nhóm các ký tự [a-zA-Z_0-9]
3.6 Tên file ảnh
Cần tìm ra tất cả những tên file ảnh có đuôi png, jpeg, jpg, gif
/(\w+\.(png|jpeg|gif|jpg))/gm
https://regex101.com/r/XRyE7d/2/
Giải thích:
.(png|jpeg|gif|jpg)
buộc chuỗi cần tìm kiếm phải có extenstion chứa hoặcpng
hoặcjpeg
hoặcgif
hoặcjpg
3.7 Địa chỉ IPv4 aaa.bbb.ccc.ddd
Địa chỉ IPv4 gồm 4 khối cách nhau bằng 3 dấu .
ví dụ: 192.168.1.1, hay 127.0.0.1
/\b(\d+).(\d+).(\d+).(\d+)\b/gm
https://regex101.com/r/hjKDOX/1
Chặt chẽ hơn nữa, mỗi khối không được vượt quá 255
/\b(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\b/gm
https://regex101.com/r/ypHIfa/1
3.8 URL http:// hoặc https://
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#()?&\/=]*)/gm
https://regex101.com/r/N3R9M0/1/
3.9 Chuỗi năm-tháng-ngày
/\b([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))\b/gm
https://regex101.com/r/FBqI8q/1
Giải thích:
[12]
: ký tự đầu tiên của năm phải là1
hoặc2
. Còn lâu mới đến năm 3xxx\d{3}
: tiếp theo đó là 3 chữ số còn lại của năm(0[1-9]|1[0-2])
: nhóm chữ số thể hiện tháng có 2 khả năng:- dưới 10:
0[1-9]
: 01,02,...,09 - trên 10:
1[0-2]
: 11, 12
- dưới 10:
(0[1-9]|[12]\d|3[01]))
: nhóm chữ số thể hiện ngày có 3 khả năng:- dưới 10:
0[1-9]
- 11 đến 29:
[12]\d
- 30 và 31:
3[01]
- dưới 10:
Kết luận
Biểu thức thường xuyên - Regex giúp bạn xử lý chuỗi, tìm kiếm, kiểm tra hợp lệ một cách nhanh chóng, dễ dàng. Một ngôn ngữ lập trình có thể không hỗ trợ Class (Go), không hỗ trợ Generic (Go), không hỗ trợ Reflection (C++), không hỗ trợ kiểu lỏng lẻo (Duckling type) nhưng nếu không hỗ trợ Regex thì nó chưa thể trở thành một ngôn ngữ lập trình. Bởi xử lý số và chuỗi là hai thao tác tối cần thiết trong mọi ngôn ngữ lập trình.
Regex không khó để hiểu. Bạn có thể quên regex nhưng một khi đã hiểu, và xem lại Cheat sheet, cùng với công cụ prototype biểu thức https://regex101.com/ bạn có thể tạo regex trong 15 phút.
Cần phải nhớ rằng, biểu thức regex không thể đảm bảo mọi trường hợp. Ví dụ trường hợp yyyy-mm-dd
, Regex sẽ chấp nhận chuỗi 2021-02-31
. Thực tế không thể có ngày 31 trong tháng 2, nhưng trong Regex chúng ta không thể viết logic để xử lý trường hợp này. Tỷ lệ khớp (matching) càng chính xác thì biểu thức regex càng dài, càng phức tạp, càng khó debug và tốn thời gian khi chạy. Do đó bạn cần kết hợp viết thành 2 cấp độ: lần đầu dùng regex, lần hai dùng code logic để xử lý những ngoại lệ rất phức tạp.
Lập trình Regex thật là đơn giản phải không các bạn. Hãy chịu khó vào blog Techmaster để xem và đóng góp bài viết nhé.
Bình luận