Regular Expression

1. Regular expression là gì

1.1 Lịch sử

Regular expression hay được gọi với tên đơn giản là biểu thức chính quy hay regex. Nó lần đầu được giới thiệu vào năm 1950s bởi một nhà toán học người Mỹ - Stephen Kleene

Biểu thức * được mang tên của ông đó là Kleene star vì những đóng góp tuyệt vời của ông với regex

Qua nhiều lần chỉnh sửa và được sự kết hợp nhiều nhà nghiên cứu và tin học khác nhau nguồn tham khảo, thì regex đã hoàn thiện và phát triển được như bây giờ

1.2 Định nghĩa

Regex là tập hợp một bộ cú pháp (pattern) giúp ích trong việc xử lí chuỗi, có thể là tìm kiếm, tìm kiếm rồi thay thế, hoặc dùng validate format trong các model nữa

Ví dụ:

/\A(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[`[email protected]#$%^&*()\-_+=\[\]{}])[0-9a-zA-Z`[email protected]#$%^&*()\-_+=\[\]{}]{10,}\z/

Bên trên là validate password với điều kiện là ít nhất một kí tự số, một kí tự in hoa, một kí tự in thường, một kí tự đặc biệt trong bộ `[[email protected]#$%^&*()-_+=]{} và phải nhiều hơn 10 kí tự

Nhìn khá đơn giản phải không =))

Ngoài ra, mình còn tìm được một bộ validate cho tiếng Nhật nếu có bạn nào cần nguồn tham khảo

Regex được hầu hết các ngôn ngữ lập trình hỗ trợ (.NET, Ruby, Python, Javascript, ....)

Với tùy từng loại ngôn ngữ thì regex lại có những tùy chỉnh khác nhau, vậy nên mọi người cần phải thử nghiệm test kĩ lại trên từng loại ngôn ngữ lựa chọn

Trong bài viết này, mình chỉ xoay quanh làm việc regex với Ruby thôi, dùng irb của ruby để chạy demo regex trên ruby

2. Regex in Ruby

2.1 Khai báo

Ruby có 3 cách chính khai báo:

  • Regexp.new('pattern', flag)

Với flag là biến cờ, tí nữa mình sẽ giới thiệu chi tiết về các biến cờ này

Ví dụ: Regexp.new('\w', Regexp::IGNORECASE)

Ngoài ra, ta có hàm Regex.complile là aslias với Regex.new, nên ta dùng các nào cũng được

  • %r{pattern}flag

Ví dụ: %r{\w}i

Ngoài ra, có thể khai báo dưới dạng %r!\w!, %r#\w#, ... miễn là kí tự bao quanh pattern là kí tự đặc biệt (mình đã thấy nhiều project khai báo kiểu này)

  • /pattern/flag

Ví dụ: /\w/i

Cách này là đơn giản và dễ nhìn nhất =))

2.2 Ứng dụng

  • Kiểm tra format hay là validate một chuỗi xem có đúng định dạng yêu cầu hay không

  • Thay thế chuỗi: gsub, gsub!, sub, sub!

Với sub thì ta sẽ thay thế một kí tự đầu tiên và dừng lại, không làm ảnh hưởng đến chuỗi ban đầu

sub! giống như sub là thay thế kí tự đầu tiên, khác ở chỗ nó áp dụng sự thay đổi vào trong chuỗi ban đầu

gsub thay thế tất cả các kí tự thỏa mãn với pattern, g trong gsub đại diện cho biến cờ global trong regex, nó cũng không làm ảnh hường đến chuỗi ban đầu

gsub! giống với gsub nhưng sẽ thay thế trực tiếp trên chuỗi ban đầu

  • Kiểm tra format =~, match?, ===

Thường thì mình hay dùng match? hơn vì nó đơn giản, nếu kết quả trả ra true thì chuỗi đó đúng chuẩn, còn false thì chuỗi đó không đúng format

Ví dụ

"024.33511603".match? /\d/ => true
"024.33511603".match? /\A\d+\z/ => false

3. Các biểu thức regex

3.1 Các biểu thức cơ bản

Kí hiệu Giải thích
[abc] So sánh bất kì kí tự nào trong dấu ngoặc vuông
[^abc] So sánh không trùng với các kí tự trong ngoặc vuông
[a-z] So sánh kí tự trong khoảng chỉ định từ a-z
[a-zA-Z] So sánh kí tự trong khoảng từ a-z và A-Z
\s So sánh kí tự khoảng trắng (space hoặc tab)
\S So sánh kí tự không phải khoảng trắng
\d So sánh kí tự là số
\D So sánh không phải là số
\w So sánh kí tự là word (chữ latin, _, chữ số)
\W So sánh kí tự không phải là word
(...) Gom nhóm
(a b)
a? So sánh 0 hoặc 1 kí tự của a
. So sánh 1 kí tự đơn bất kì ngoài unicode
a* So sánh 0 hoặc nhiều hơn kí tự của a
a+ So sánh 1 hoặc nhiều hơn kí tự của a
a{3} So sánh với đúng 3 lần của của a
a{3,6} So sánh với đúng 3 đến 6 lần của của a
a{3,} So sánh với đúng từ 3 lần trở lên của a
\b Khớp với kí tự đằng trước nó (giới hạn)
\B Không khớp với kí tự đằng trước nó (giới hạn)

Ngoài ra, mình còn tổng hợp một số phần mở rộng ở link

3.2 Lưu ý

  • Khi muốn tìm lấy giá trị hoặc thay thế, bạn nên nhóm phần tử đó lại trong group (nằm trong giữa 2 ngoặc đơn ())

  • Khi muốn kiểm tra format đúng hay sai, nên thêm những cú pháp neo (anchor) hoặc giới hạn (\b\b) để có thể kiểm tra được chính xác nhất

  • Các kí tự đặc biệt cần thêm \ đằng trước: , ^, $, ., |, ?, *, +, (, ), [, {

4. Bài tập

Mình có chuẩn bị một số bài tập để mọi người vừa làm vừa hiểu hơn về regex

1. Ứng dụng format số điện thoại trong danh bạ, nếu có số 0 đằng trước sẽ format thành +84

0978135000 -> +84978135000

  • Bài giải:

Đây là dạng bài thay thế kí tự đầu tiên, vậy nên mình sẽ sử dụng hàm sub hoặc sub! là đủ

Regex mình viết là: \A0

Đáp án đầy đủ là: "0978135000".sub /\A0/, "+84"

  • Giải thích:

Bài toán này đơn giản chỉ là thay thế một kí tự nên cũng khá đơn giản, nhưng vẫn còn một chỗ cần lưu ý

Nó phải là kí tự đầu tiên, vậy nên mình có bổ sung thêm \A ở đầu regex (Mình có note trong lưu ý ở bên trên rồi), nó sẽ không thay thế nếu số 0 đó không nằm ở đầu chuỗi

Done, dễ phải không nào, tiếp tục bài 2 nhá

2. Cho Data sau:

thịt bò: 100.000

thịt lợn: 120.000

thịt gà: 80.000

Thay đổi data trên, vì cửa hàng muốn thể hiện là thịt của mình là hàng xịn, nên muốn thêm chữ (chính hãng) vào sau tên thịt, bổ sung đơn vị tiền tệ đằng sau giá tiền VND/kg

thịt bò: 100,000 -> thịt bò (chính hãng): 100.000 VND/kg

  • Bài giải:

Bài này kết hợp cả tìm kiếm và thay thế chuỗi

Đầu tiên, ta phải tìm được format chung của chuỗi trên, sau đó thay thế thành thế thành chuỗi mình muốn

Mình sẽ sử dụng lazy matching để lấy tên thịt và số tiền tương ứng: (.*):(.*) (bạn nên test trên rubular trước)

Sau đó sẽ thay thế bằng chuỗi /(.*):(.*)/, '\1 (chính hãng): \2 VND/kg'

  • Giải thích:

Mình sử dụng gom lazy matching .* và gom nhóm kết hợp () (có trong note rồi đó)

Khi mình gom nhóm rồi thì sẽ gọi được \1 tương ứng với tên thịt và \2 tương ứng với giá thịt

Cuối cùng thì mình chỉ cần thêm thắt dữ liệu cần thay đổi thôi, đơn giản phải không nào =))

3. Kiểm tra format email sun-asterisk

nguyen.quang.vinhb @sun-asterisk.com

[email protected]

[email protected]

[email protected]

[email protected]

[email protected]

[email protected]@sun-asterisk.com

-> format đúng là: [email protected]

  • Bài giải:

https://regex101.com/r/b2LgHj/2

^\w+(?:\.\w+){0,}@sun-asterisk\.com$

  • Giải thích

Đầu tiên thì mình sẽ tìm email có định dạng phần đuôi đúng với sun-asterisk đó là @sun-asterisk\.com$

Tiếp đó, email phải được bắt đầu bằng kí tự chữ ^\w+

Cuối cùng là format một dấu chấm và một cụm từ (?:\.\w+){0,}, cái này bắt được luôn điều kiện là trước kí tự @ phải là một kí tự

5. Tổng kết

Trên đây là những tìm hiểu của mình về regular expression, hi vọng giúp ích được cho mọi người

Cảm ơn mọi người đã theo dõi.

6. Tài liêu tham khảo

https://viblo.asia/p/hoc-regular-expression-va-cuoc-doi-ban-se-bot-kho-updated-v22-Az45bnoO5xY

https://regex101.com/

https://gist.github.com/terrancesnyder/1345094

https://www.rexegg.com/regex-quantifiers.html

7. Bài tập bonus

Hãy tìm chữ có nghĩ thỏa mãn regex bên dưới, bạn nào làm xong hãy comment bên dưới nha

(?(?=F)Fl|Th)e?y?\sb(?:o\w|\w{3})l$

Đáp án test trên regex101.com là chuẩn nhất vì khi test trên rubular, họ nói mình làm rubular buồn 😦

Thankss all !!!!


All Rights Reserved