® Bài viết này của lazycat - hocautoit.com ®
Regular Expressions (Regex) dịch ra tiếng Việt là Biểu thức chính quy. Khái niệm này nằm trong 1 mớ lý thuyết vô cùng đồ sộ và hầm hố. Nhưng không nên lo lắng, ta có thể hiểu nôm na Regex là 1 cái mẫu (pattern) dùng để mô tả 1 lớp ký tự nào đó. Ví dụ: lazydog là 1 regex. Nó là 1 mẫu đơn giản nhất vì nó so khớp (match) với đoạn text lazydog. 1 match là 1 đoạn text so khớp với mẫu.
Ví dụ phức tạp hơn 1 chút: \b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b . Đây là mẫu mô tả 1 địa chỉ email. Mẫu này có thể được dùng để tìm 1 địa chỉ email trong 1 đoạn văn bản, hoặc kiểm tra xem 1 chuỗi có phải là địa chỉ email hợp lệ hay không.
Regex có thể được sử dụng với bất kỳ dữ liệu nào mà ta có thể truy cập, thông qua ứng dụng hoặc ngôn ngữ lập trình. Có thể kể đến 1 số ứng dụng xử lý văn bản hỗ trợ regex: PowerGREP, EditPad Pro, RegexBuddy,...
Regular Expression Engines
Regex engine là 1 bộ phận của phần mềm, chuyên để xử lý regex (so khớp mẫu với 1 chuỗi nào đó). Có nhiều regex engine và chúng không hoàn toàn tương thích với nhau. Cú pháp regex (flavor) của mỗi engine cũng có sự khác nhau. Loạt bài này sẽ tập trung vào cú pháp regex được sử dụng trong Perl 5, vì nó phổ biến nhất. Rất nhiều engine regex khác giống với engine sử dụng trong Perl 5: engine nguồn mở PCRE (sử dụng trong rất nhiều ngôn ngữ lập trình, và cả AutoIT cũng dùng thằng này), thư viện regex.NET,...
Để dễ tương tác, mình sẽ đánh dấu những string được áp dụng regex bằng màu xanh, các regex bằng màu hồng và phần match sẽ là màu lam
Regex Engine của AutoIt thuộc loại Regex-directed engine và các bạn nhớ lưu ý điều này khi đọc các TUT khác về regex (vì còn 1 loại nữa là text-directed engines). Đặc điểm của Regex-directed engine là "luôn trả về kết quả so khớp bên trái nhất" thậm chí nếu có 1 match khác tốt hơn phía sau! Điều này đáng để ghi nhớ! Regex-directed engine luôn bắt đầu so khớp với ký tự đầu tiên của chuỗi. Giả sử ta áp dụng regex cat và chuỗi He captured a catfish for his cat. Thì nó sẽ match He captured a catfish for his cat. rồi dừng lại thay vì He captured a catfish for his cat.
Trong AutoIt thì chỉ có 2 hàm chính liên quan đến regex đó là StringRegExp và StringRegExpReplace. 2 hàm này tương tự nhau (về phần regex) nên ở đây chúng ta sẽ nói đến StringRegExp trước.
Hàm StringRegExp
Hàm StringRegExp trả về Mảng khi flag > 0, trả về True/False khi flag = 0
· | "test"=Chuỗi cần áp dụng regex (string) |
· | "pattern"=Một chuỗi các kí tự khóa để chỉ cho hàm biết chính xác các bác muốn khớp (match) cái gì. Không if, không and và không but. Chỉ có match hoặc không. (biểu thức regex) |
flag[optional]=Chỉ cho hàm rằng các bác chỉ muốn biết xem là biểu thức regex có match hay không, hoặc trả về các kí tự các bác muốn lấy từ biểu thức regex
flag=0: kiểm tra xem pattern có khớp với string hay không.
Ví dụ trên sẽ trả về 0 vì không tìm thấy test trong text (không match)
Ví dụ trên sẽ trả về 1 vì tìm thấy ex trong text (match)
flag=1: trả về (array) nhóm đầu tiên mà pattern đã khớp với string
$ex=StringRegExp ("text and test" , 'te(.)t' , 1)
MsgBox (0 , "Regex ex" , $ex[ 0 ])
|
Ví dụ trên sẽ trả về 1 mảng có nội dung vì . match với x và đã được nhóm (sẽ nói đến sau)
$ex=StringRegExp ("text and test" , 't(.)(.)t' , 1)
MsgBox (0 , "Regex ex" , $ex[ 0 ])
MsgBox (0 , "Regex ex" , $ex[ 1 ])
|
Ví dụ trên sẽ trả về 2 mảng có nội dung e và x vì . match với e và x và đã được nhóm
flag=2: trả về (array) toàn bộ pattern đã khớp với string
$ex=StringRegExp ("text" , 'te(.)t' , 2)
MsgBox (0 , "Regex ex" , $ex[ 0 ])
|
Ví dụ trên sẽ trả về 1 mảng có nội dung text
$ex=StringRegExp ("text" , 'te(.)' , 2)
MsgBox (0 , "Regex ex" , $ex[ 0 ])
|
Ví dụ trên sẽ trả về 1 mảng có nội dung tex
flag=3: trả về (array) tất cả các nhóm mà pattern đã khớp với string
$ex=StringRegExp ("text and test" , 'te(.)t' , 1)
MsgBox (0 , "Regex ex" , $ex[ 0 ])
MsgBox (0 , "Regex ex" , $ex[ 1 ])
|
Ví dụ trên sẽ trả về 2 mảng có nội dung x và s vì . match với x và s và đã được nhóm
Những loại pattern
Character Classes - Các lớp kí tự
Khớp một kí tự bất kì, ngoài trừ ngắt dòng (xuống dòng) nếu không sử dụng chế độ single-line mode (?s)
Khớp mọi chữ số, chữ cái ASCII và dấu gạch _ (gạch dưới): a đến z, A đến Z. Không khớp với kí tự Unicode (có dấu): ờ, ế, à,.....
Tương đương với [a-zA-Z_]
Ngược lại với \w. Khớp mọi kí tự không phải chữ số, chữ cái ASCII và dấu gạch _ (gạch chân). Khớp với kí tự Unicode
Tương đương với [^a-zA-Z_]
Khớp mọi chữ số: 0 đến 9
Tương đương với [0-9]
Ngược lại với \d. Khớp mọi kí tự không phải là chữ số.
Tương đương với [^0-9]
o | \s (Space - khoảng trống) |
Khớp mọi kí tự là khoảng trắng: từ Chr(09) đến Chr(13) là Tab ngang, line feed, tab dọc, form feed, carriage return và cả space() hay Chr(32)
Ngược lại với \s
;~ Ví dụ các bạn muốn lấy số 1000 trong string dưới.
$string="Hiện tại có 1000 người đang đọc TUT này.. há há"
$regex=StringRegExp ($string , "(\d)+" , 1)
MsgBox (0 , "Test" , "Số người đang đọc TUT: " & $regex[ 0 ])
|
Character Sets - Các bộ kí tự
o | Bộ kí tự được đánh dấu và giới hạn bởi 2 dấu ngoặc vuông đóng và mở [ và ] |
o | Regex engine chỉ chọn ra 1 kí tự trong bộ kí tự để so khớp hay không. Nếu kí tự đầu không khớp thì xét đến lượt kí tự 2 trong bộ, cứ thế cho đến hết. |
o | Mỗi bộ kí tự chỉ khớp với một kí tự |
o | Có thể dùng dấu gạch ngang - giữa kí tự đầu và cuối trong bộ để chỉ ra một tập hợp. Thứ tự các tập hợp không ảnh hưởng đến kết quả match |
o | Khi thêm ^ ở sau [ ([^) thì cả bộ sẽ trở thành phủ định. Khớp với kí không ở trong bộ. |
#include <array.au3>
;~ Ví dụ về Regex engine chỉ chọn ra 1 kí tự trong bộ kí tự, mỗi kí tự trong bộ chỉ khớp vs 1 kí tự
$regex1=StringRegExp ("Có 1 con mèo" , "[12]" , 3)
$regex2=StringRegExp ("Có 2 con mèo" , "[12]" , 3)
_ArrayDisplay($regex1)
_ArrayDisplay($regex2)
;Khi để trong bộ [ ] thì regex engine chỉ chọn từng kí tự một trong bộ để so khớp, nếu không hiểu lắm, xem ví dụ dưới
$regex3=StringRegExp ("Có 12 con mèo" , "[12]" , 3)
_ArrayDisplay($regex3)
;Lúc này nó sẽ trả về 2 mảng khớp, 1 mảng có giá trị là 1, mảng kia là 2
;Vì sao nói "Mỗi bộ kí tự chỉ khớp với một kí tự" mà ở đây lại khớp đến 2??? Vì đúng là như vậy! Nó không trả về 1 mảng có giá trị 12 mà trả về 2 mang như đã nói ở trên
;~ Ví dụ về sử dụng dấu gạch ngang -
$regex4=StringRegExp ("Mã màu đỏ: #FF0000, mã màu vàng #FFFF00" , "[A-F0-9]{6}" , 3)
_ArrayDisplay($regex4)
;Regex trên sử dụng [ ] để match những chữ in hoa từ A đến F và 0-9 thay vì phải dùng [ABCDEF0123456789] ;///{6} sẽ được nói đến ở phần dưới
;~ Ví dụ về sử dụng dấu mũ ^ (dấu mũ trong [ ] mang ý nghĩa phủ định)
$regex5=StringRegExp ("123456789" , "[^1-5]+" , 3)
_ArrayDisplay($regex5)
;Regex trên sẽ trả về dãy không chứa các số từ 1 đến 5
;Lưu ý: khi dùng dấu ^ thì cả bộ sẽ mang ý nghĩa phủ định. Ví dụ các bác dùng [^1-37-9] để khớp các số từ 7 đến 9 và bỏ các số từ 1 đến 3
$regex6=StringRegExp ("123456789" , "[^1-37-9]+" , 3)
_ArrayDisplay($regex6)
;Ố ồ nó trả về 456 lol. Các bác tưởng là regex nó bị khìn khìn?? đừng hoang tưởng thế! [^1-37-9] tương tự [^123789] và được hiểu là loại bỏ các số từ 1 đến 3 và từ 7 đến 9
|
Nonprintable Characters - Kí tự không in được
o | \a khớp với kí tự BEL hay Chr(7)==>nhỏ tới h chưa biết mặt mũi thằng này :oops: |
o | \c khớp với kí tự điều khiển ví dụ \cM khớp với Ctrl-M |
o | \e khớp với kí tự escape hay Chr(27) |
o | \f khớp với form feed hay Chr(12) |
o | \h khớp với horizontal tab hay Chr(13) |
o | \H ngược lại với \h, khớp bất kì kí tự trừ horizontal tab |
o | \n khớp với line feed hay @LF |
o | \r khớp với carriage return @CR |
o | \t khớp với tab hay @TAB |
o | \v khớp với vertical tab hay Chr(11) |
o | \V ngược lại với \v, khớp bất kì kí tự trừ vertical tab |
o | \xhh khớp với kí tự có mã hex là hh |
;~ Bác nào muốn tỏ vẻ nguy hiểm thì mấy kí tự đưa hết về hex, rồi dùng \x hí hí
#include <array.au3>
$regex=StringRegExp ("I'm Kingofstars", "\x4B\x69\x6E\x67\x6F\x66\x73\x74\x61\x72\x73", 3)
_ArrayDisplay($regex)
|
Special Characters - Kí tự đặc biệt
o | Để match với các kí tự đặc biệt này, xem phần Escaped character |
o | \ dùng để giải phóng (thoát) các kí tự đặc biệt nên nó được xem là kí tự đặc biệt |
o | Ngoài ra còn. + * ? ^ $() [ ] { } |
#include <array.au3>
$regex=StringRegExp ("C:\Windows\System32" , "C:\" , 3)
_ArrayDisplay($regex)
;Sao chạy chẳng hiện gì nhỉ??? Vì có gì để hiện đâu mà hiện lol. \ là một trong các kí tự đặc biệt thế nên ta phải "giải phóng" nóa
$regex1=StringRegExp ("C:\Windows\System32" , "C:\\" , 3)
_ArrayDisplay($regex1)
;Okie roài đó
;Đến | thì em nói luôn, thằng này xài rất linh động nhóa. hị hị
$regex2=StringRegExp ("test" , "test|text" , 3)
_ArrayDisplay($regex2)
;Regex trên khớp cả test và text; có thể dùng [ ] cho gọn
;Mở rộng: với regex te(s|x)t|teen thì các bác hiểu thế nào, hí hí
$regex3=StringRegExp ("test" , "te(s|x)t|teen" , 3)
_ArrayDisplay($regex3)
;Nó sẽ match test
$regex4=StringRegExp ("text" , "te(s|x)t|teen" , 3)
_ArrayDisplay($regex4)
;Match luôn với text
$regex5=StringRegExp ("teen" , "te(s|x)t|teen" , 3)
_ArrayDisplay($regex5)
;Và teen cũng không nằm ngoài tầm ngắm!
;Cách nó hoạt động: đầu tiên sẽ tách regex làm 2 chuỗi: match với te(s|x)t hoặc teen vì | thứ 2 không nằm trong nhóm () nên quyền ưu tiên cao hơn
;Tiếp đến te(s|x)t nó sẽ dò từng kí tự một, đầu tiên là t rồi e rồi đến s; nếu s không thấy thì dò x; nếu x cũng không thấy trong string thì next đến teen, không thì dò tiếp chữ t...
|
Escaped Characters - Kí tự thoát
Để giải phóng một kí tự đặc biệt, ta dùng \ trước nó hoặc đặt giữa \Q và \E. Khi cần match nhiều kí tự đặc biệt liên tiếp, ta dùng nên \Q\E để dễ làm việc.
Danh sách các kí tự đặc biệt cần escaped khi muốn match:
o | \\ hoặc \Q\\E match với \ |
o | \. hoặc \Q.\E match với . |
o | \+ hoặc \Q+\E match với + |
o | \* hoặc \Q*\E match với * |
o | \? hoặc \Q?\E match với ? |
o | \^ hoặc \Q^\E match với ^ |
o | \$ hoặc \Q$\E match với $ |
o | \( hoặc \Q(\E match với ( |
o | \) hoặc \Q)\E match với ) |
o | \[ hoặc \Q[\E match với [ |
o | \] hoặc \Q]\E match với ] |
o | \{ hoặc \Q{\E match với { |
o | \} hoặc \Q}\E match với } |
o | \| hoặc \Q|\E match với | |
Anchors - Móc neo
Nghe tên chắc các bác cũng hình dung được. Các móc neo sẽ không match với gì cả, tác dụng của nó là móc vào đầu hoặc cuối của chuỗi hoặc móc vào đầu, cuối của từ,.....
o | ^ hoặc \A sẽ móc regex vào phần đầu của chuỗi (không phải nhất thiết là đầu dòng) |
o | $ hoặc \Z sẽ móc regex vào phần cuối của chuỗi hoặc móc vào phía trước newline ở cuối chuỗi (không phải nhất thiết là cuối dòng) |
o | \z sẽ móc regex vào phần cuối của chuỗi |
o | \b sẽ móc regex vào phần đầu hoặc cuối của từ hoặc chỉ phía đầu/cuối của một từ |
o | \B ngược lại với \b, nó sẽ móc regex vào giữa một từ |
#include <array.au3>
$regex=StringRegExp ("Dòng này là dòng thứ nhất." & @CRLF & "dòng thứ 2" , "^dòng" , 3)
_ArrayDisplay($regex)
;Ví dụ trên trả về blank vì: ^ móc vào đầu chuỗi, đầu chuỗi có từ Dòng (chữ D hoa) mà regex thì chữ d thường → không match
;Có thể dùng \A để thay thế nhưng nhớ là A in hoa
$regex1=StringRegExp ("Dòng này là dòng thứ nhất." & @CRLF & "dòng thứ 2" , ".$" , 3)
_ArrayDisplay($regex1)
;Ví dụ trên trả về 2 vì: $ móc vào cuối chuỗi mà trước khi kết thúc chuỗi là kí tự 2, dấu. match mọi kí tự → trả về 2 hế hế
;Có thể dùng \Z để thay thế nhưng nhớ là Z in hoa
;~ Sự khác nhau giữa \z và \Z
$regex2=StringRegExp ("Dòng thứ 2" & @CRLF , ".\Z" , 1)
_ArrayDisplay($regex2)
;Ví dụ trên trả về 2 mặc dù 2 không phải là cuối chuỗi (cuối là \r\n)
$regex3=StringRegExp ("Dòng thứ 2" & @CRLF &"a" , ".\Z" , 1)
_ArrayDisplay($regex3)
;Trả về a → chỉ match nếu cuối chuỗi có newline hoặc không có gì cả
$regex4=StringRegExp ("Dòng thứ 2" & @CRLF , ".\z" , 1)
_ArrayDisplay($regex4)
;Ví dụ này thì lại không trả về gì cả, đó là sự khác biệt. Các bác nhớ lấy điều này
;~ Ví dụ về \b
$regex5=StringRegExp ("I'm Kingofstars001. I'm showing you how regex works and how to controll it." , "how" , 3)
_ArrayDisplay($regex5)
;Ví dụ trên sẽ trả về 3 từ how nhưng ta chỉ thấy 2 từ, từ còn lại nằm trong s{how}ing. Khi thêm \b trước chữ h thì sẽ trả về chỉ 2 mà thôi
$regex6=StringRegExp ("I'm Kingofstars001. I'm showing you how regex works and how to controll it." , "\br\w*x\b" , 3)
_ArrayDisplay($regex6)
;Như các bác thấy, nó sẽ trả về từ regex; phân tích regex: \b là móc vào đầu từ r là kí tự đầu tiên của từ \w* là tìm nhiều chữ số và _ nhiều nhất có thể, x là chữ kết thúc, \b là móc vào cuối từ
|
Lookaround - Ngắm xung quanh
Cái này giúp tương tác xung quanh regex, đằng trước, đằng sau regex hoặc không cả 2
(?<=XYZ) match một nhóm sau regex của các bác nhưng không bao gồm regex trong kết quả. Ví trong một bầy chó, các bác muốn bắt con nào dê nhất thì phải tìm xem con nào nhìn mặt dê nhất => đầu nó lắp vào XYZ trên kia, phần thân sẽ là phần match>:)
(?!ABC) ngược lại với (?=ABC), match một nhóm mà phía sau nó không có cục regex của các bác.
#include <array.au3>
$regex=StringRegExp ("I'm Kingofstars001 and i'm showing you how regex works and how to controll it." , "(?<=you)\bhow" , 3)
_ArrayDisplay($regex)
;Ví dụ trên trả về how mà phía trước có chữ you
$regex1=StringRegExp ("I'm Kingofstars001 and i'm showing you how regex works and how to controll it." , "\bhow (?=to)" , 3)
_ArrayDisplay($regex1)
;Ví dụ trên chỉ trả về 1 chữ how vì chỉ có 1 chữ how mà đằng sau nó có chữ to
|
Groups - Nhóm
o | (XYZ) dùng để nhóm các kí tự trong nó lại (để sử dụng với repeating characters) và nếu dùng flag khác 0 thì sẽ trả về kết quả những kí tự được match trong nhóm. |
o | (?:XYZ) dùng để nhóm các kí tự trong nó lại (để sử dụng với repeating characters) và sẽ không trả về kí tự được match. |
o | (?i:XyZ) nhóm kí tự nhưng không trả về và không phân biệt hoa thường (xem phần optional setting) |
o | (?-i:XYZ) nhóm kí tự nhưng không trả về và phân biệt hoa thường (xem phần optional setting) |
#include <array.au3>
$regex=StringRegExp ("Hiện tại có 100 người đang đọc thớt này" , "có (\d*)" , 3)
_ArrayDisplay($regex)
;Ví dụ trên trả về 100 vì \d* match với số 100 và đã được nhóm
$regex1=StringRegExp ("Hiện tại có 100 người đang đọc thớt này" , "có (?:\d*)" , 3)
_ArrayDisplay($regex1)
;Ví dụ trên chỉ trả về toàn bộ regex đã match vì không có kí tự được nhóm
|
Repeating Characters - Kí tự nhắc lại
o | {x} lặp lại kí tự, nhóm, bộ kí tự đằng trước đúng x lần. |
o | {x,} lặp lại kí tự, nhóm, bộ kí tự đằng trước ít nhất x lần. |
o | {0,x} lặp lại kí tự, nhóm, bộ kí tự đằng trước nhiều nhất x lần. |
o | {x,y} lặp lại kí tự, nhóm, bộ kí tự đằng trước trong khoảng từ x đến y lần. |
o | * tương đương với {0,} lặp lại kí tự, nhóm, bộ kí tự đằng trước 0 lần hoặc nhiều hơn và sẽ match nhiều nhất có thể (có cũng match, không có cũng match). |
o | + tương đương với {1,} lặp lại kí tự, nhóm, bộ kí tự đằng trước 1 lần hoặc nhiều hơn và sẽ match nhiều nhất có thể (phải xuất hiện ít nhất 1 lần mới match). |
o | ? tương đương với {0,1} kí tự, nhóm, bộ kí tự trước nó có thể xuất hiện hoặc không. |
o | ? đứng sau một trong những cái trên (repeating characters) sẽ match ít nhất có thể thay vì nhiều nhất. |
#include <array.au3>
$regex=StringRegExp ("Có 111 con chó đốm." , "(\d{2})" , 3)
_ArrayDisplay($regex)
;Chỉ trả về 11 vì \d chỉ được lặp 2 lần
$regex1=StringRegExp ("Có 111 con chó đốm." , "(\d{4,})" , 3)
_ArrayDisplay($regex1)
;Không trả về vì chỉ có 3 số mà {4,} bắt buộc ít nhất là 4
$regex2=StringRegExp ("color" , "colou?r" , 3)
_ArrayDisplay($regex2)
;Mặc dù trong string không có chữ u nhưng chuỗi vẫn được trả về vì ? đã qui định chữ u không có hoặc chỉ có 1 lần đều được
$regex3=StringRegExp ("When you're gone, the pieces of my heart are missing you. And when you're gone, the face I came to know is missing too." , "(.*)," , 1)
_ArrayDisplay($regex3)
;Ví dụ trên nó sẽ trả về từ dầu đến And when you're gone, (đến dấu phẩy thứ 2 - dấu phẩy xa nhất), nếu bác muốn chỉ trả về đến dấu phẩy gần nhất?? hãy thêm dấu ? vào sau *
$regex4=StringRegExp ("When you're gone, the pieces of my heart are missing you. And when you're gone, the face I came to know is missing too." , "(.*)," , 1)
_ArrayDisplay($regex4)
;Và nó đã nghe lời bác rồi đó
|
Internal Option Setting - Thiết lập tùy chọn
o | (?i) trường hợp không nhạy cảm (case-insensitivity), được hiểu là không phân biệt viết HOA và thường |
o | (?-i) trường hợp nhạy cảm (case-sensitivity), được hiểu là phân biệt viết HOA và thường (mặc định luôn) |
o | (?m) nhiều dòng (multiline), khi sử dụng con này, móc neo ^ và $ sẽ match ở đầu và cuối câu chứ không chỉ match ở đầu và cuối chuỗi nữa. |
o | (?s) chế độ single-line (còn gọi là dotall), khi được sử dụng sẽ làm. match mọi kí tự kể cả xuống dòng=>2 con này đi với nhau là uy lực nhất nếu biết cách sử dụng>:) |
o | (?x) chế độ mở rộng (extended) bỏ qua khoảng trắng và # trong regex |
Có vài lưu ý mà các bác cần chú ý:
1. Regex luôn trả về phần match gần bên trái nhất
2. \x và \X không phải lúc nào cũng đối nhau: \s đối với \S nhưng \z và \Z thì khác hoàn toàn
3. Có 1 vài kí tự ở vị trí khác nhau thì sẽ mang ý nghĩa khác nhau: ^ và ? và |
4. Cụm .* (có thể gọi là cụm lười biếng) có thể giải quyết rất nhiều vấn đề, hãy xài nó đúng lúc, đúng chỗ và nhớ là luôn phải tiết kiệm.
|
|