Ngôn ngữ SNOBOL được sử dụng đầu tiên trong việc so khớp mẫu, nhưng nó cũng không giống hoàn toàn với biểu thức chính quy.. Kể từ đó nhiều biến thể của biểu thức chính quy do Thompson đư
Trang 1I
TRƯỜNG ĐẠI HỌC BÁCH KHOA HÀ NỘI VIỆN CÔNG NGHỆ THÔNG TIN VÀ TRUYỀN THÔNG
BÀI TẬP LỚN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH Giáo viên hướng dẫn: TS Nguyễn Hữu Đức
Học viên thực hiện : Trương Thảo Nguyên CB120098
Nguyễn Thị Thùy Liên CB120046
Chuyên ngành: Công nghệ thông tin (KT)
Đề bài: Biểu thức chính quy, thư viện biểu thức chính quy trong
các ngôn ngữ lập trình Java, PHP, NET
HÀ NỘI 11 – 2012
Trang 2MỤC LỤC
1 Biểu thức chính quy 4
1.1 Khái niệm 4
1.2 Đặc điểm 4
1.2.1 Ưu điểm 4
1.2.2 Nhược điểm 4
1.3 Lịch sử phát triển 4
1.4 Các kiến thức cơ bản về biểu thức chính quy 5
1.4.1 Cách thức họat động của biểu thức chính quy 5
1.4.2 Kí tự thông thường và kí tự đặc biệt 6
1.4.2.1 Kí tự thông thường 6
1.4.2.2 Kí tự đặc biệt 6
1.4.3 Tìm kiếm theo vị trí 7
1.4.3.1 Neo đầu và neo cuối chuỗi 7
1.4.3.2 Sử dụng ^ và $ để neo đầu và cuối 1 dòng 8
1.4.3.3 Neo đầu và cuối chuỗi vĩnh cửu 8
1.4.4 Các lớp ký tự 8
1.4.4.1 Lớp ký tự phủ định 9
1.4.4.2 Metacharacter trong lớp ký tự 9
1.4.4.3 Lớp ký tự viết tắt (Shorthand Character Classes) 10
1.4.4.4 Lớp ký tự viết tắt phủ định (Negated Shorthand Character Classes) 10
1.4.5 Biểu thức lặp theo độ dài 10
1.4.6 Phân nhóm 11
2 Thư viện biểu thức chính quy trong ngôn ngữ lập trình PHP 11
2.1 Thư viện PCRE l(Perl-Compatible Regular Expressions) 11
2.2 Các nhóm hàm của regular expression trong PHP 12
2.2.1 Tập các hàm preg 12
2.2.1.1 Hàm preg_match 12
2.2.1.2 Hàm preg_match_all 12
2.2.1.3 Hàm preg_grep 13
2.2.1.4 Hàm preg_repalce 13
Trang 32.2.1.5 Hàm preg_ repalce _callback 13
2.2.1.6 Hàm preg_split 13
2.2.2 Tập các hàm ereg 14
2.2.2.1 Hàm ereg 14
2.2.2.2 Hàm ereg_replace 14
2.2.2.3 Hàm split 14
2.2.3 Tập các hàm mb_ereg 14
3 Tài liệu tham khảo 15
Trang 41 Biểu thức chính quy
1.1 Khái niệm
Biểu thức chính quy ( regular expression viết tắt là regexp, reges hay regxp) là một chuỗi miêu tả một bộ các chuỗi khác, theo những quy tắc cú pháp nhất định
1.2 Đặc điểm
1.2.1 Ưu điểm
Giúp đơn giản hơn trong lập trình và quá trình xử lý văn bản ( Thay vì cần đến hàng trăm thủ tục để có thể trích xuất tất cả các địa chỉ email từ một số tài liệu, với regular expression chỉ cần một số dòng lệnh hoặc thậm chí một dòng lệnh để làm việc này
Giúp tiết kiệm thời gian và công sức
1.2.2 Nhược điểm
Gây phiền toái không mong muốn như: sử dụng một biểu thức chính quy không phù hợp với
biểu thức muốn tìm hoặc số văn bản tìm được với biểu thức chính quy đó không phù hợp
1.3 Lịch sử phát triển
Nguồn gốc của biểu thức chính quy nằm ở lý thuyết tự động và lý thuyết ngôn ngữ hình thức,
cả hai đều là một phần của khoa học máy tính Do vậy có thể nói thuật ngữ regular expression xuất phát từ lý thuyết toán học và khoa học máy tính, nó phản ánh một đặc điểm của các biểu thức toán học được gọi là chính quy (regularity)
Trong những năm 1950, nhà toán học Stephen Cole Kleene mô tả các mô hình này bằng cách
sử dụng các ký hiệu toán học của ông được gọi là tập chính quy
Ngôn ngữ SNOBOL được sử dụng đầu tiên trong việc so khớp mẫu, nhưng nó cũng không giống hoàn toàn với biểu thức chính quy Sau đó Ken Thompson đã xây dựng các ký hiệu Kleene vào trình soạn thảo QED để so khớp mẫu trong các file văn bản Sau đó ông thêm khả năng này vào trình soạn thảo Unix, mà cuối cùng đã trở thành công cụ tìm kiếm grep rất phổ biển được sử dụng cho biểu thức chính quy (grep là một từ có nguồn gốc từ những lệnh của biểu thức chính quy tìm kiếm: g/re/p viết tăt cho biểu thức chính quy) Kể từ đó nhiều biến thể của biểu thức chính quy do Thompson đưa ra được sử dụng rộng rãi trong Unix và các hệ Unix-like bao gồm
cả expr, AWK, Emacs
Trang 5Biểu thức chính quy của Perl và Tcl được bắt nguồn từ thư viện regex viết bởi Henry Spencer, mặc dù sau đó Perl đã mở rộng thư viện này để thêm nhiều tính năng mới Philip Hazel phát triển PCRE(Perl Compatible Regular Expressions – biểu thức chính quy tương thích Perl), dựa trên biểu thức chính quy của Perl và được sử dụng trong nhiều công cụ hiện đại như PHP và Apache HTTP Server Một phần của những nổ lực trong việc thiết kế Perl6 là cải thiện, phát triển biểu thức chính quy Perl tích hợp và tăng phạm vi và khả năng để cho phép định nghĩa được các biểu hiện phân tích cú pháp Kết quả cho ra đời mini-language gọi là Perl 6 được sử dụng để định nghĩa cú pháp cũng như cung cấp mọt công cụ để lập trình bằng ngôn ngữ
1.4 Các kiến thức cơ bản về biểu thức chính quy
1.4.1 Cách thức họat động của biểu thức chính quy
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 Chúng ta 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, như PHP), thư viện regex NET,
Hiểu được cách làm việc của regex engine sẽ giúp ta viết regex tốt hơn, dễ dàng hơn Nó giúp ta hiểu được tại sao 1 regex hoạt động không như mong muốn, và giúp tiết kiệm thời gian phải mò mẫm khi viết các regex phức tạp
Có 2 loại regex engine: text-directed engines, và regex-directed engines Loại cú pháp regex
mà ta đang thảo luận ở đây thuộc loại regex-directed engines Loại engine này phổ biến hơn bởi
nó có 1 số chức năng rất hữu dụng như: lazy quantifiers, backreferences,
Có thể dễ dàng kiểm tra xem loại cú pháp đang sử dụng thuộc về engine nào qua việc kiểm tra xem lazy quantifiers và backreferences có được hỗ trợ không Hãy thử dùng biểu thức regex regex|regex not vào chuỗi regex not xem sao Nếu kết quả so khớp là regex, thì engine đang dùng thuộc loại regex-directed Nêu kết quả là regex not, thì engine thuộc loại text-directed Trong các
VD ở các bài tiếp theo, ta sẽ phân tích cụ thể cách thức làm việc của regex engine, qua đó giúp
sử dụng regex hiệu quả nhất và tránh mắc lỗi
Regex-directed engine luôn trả về kết quả so khớp bên trái nhất
Trang 6Thậm chí nếu 1 match tốt hơn có thể được tìm thấy nếu tiếp tục so khớp Đây là điều cần ghi nhớ Regex-directed engine luôn bắt đầu so khớp với ký tự đầu tiên của chuỗi Hãy lấy 1 VD đơn giản nhất để minh hoạ: ta dùng regex cat vào chuỗi He captured a catfish for his cat Engine
sẽ bắt đầu so khớp dấu hiện đầu tiên trong regex là c với ký tự đầu tiên của chuỗi là H Không khớp Vì vậy nó tiếp tục lần lượt so khớp với ký tự thứ 2 và 3 là e và space Đều không khớp Đến ký tự thứ 4, c đã khớp với c Xong, giờ engine bắt đầu so khớp dấu hiệu thứ 2 trong regex là
a với ký tự thứ 5 của chuỗi là a Khớp Nhưng đến dấu hiệu thứ 3 của regex là t thì không khớp với ký tự thứ 6 của chuỗi là p Lúc này engine ngộ ra rằng không thể tìm ra 1 match bắt đầu từ
ký tự thứ 4 của chuỗi Vì vậy, nó bắt đầu lại công việc từ đầu, từ ký tự thứ 5 của chuỗi Regex c không khớp với a Cứ tiếp tục như vậy cho đến ký tự thứ 15 của chuỗi, regex c đã khớp với c Engine lần lượt so khớp các dấu hiệu còn lại trong regex với các ký tự tiếp theo trong chuỗi: a khớp a, t khớp t Và như vậy 1 match đã được tìm thấy bắt đầu từ ký tự 15 Engine sẽ trả về kết quả và ngừng luôn, không tiếp tục tìm xem còn match nào tốt hơn không (VD: cat ở cuối chuỗi)
1.4.2 Kí tự thông thường và kí tự đặc biệt
1.4.2.1 Kí tự thông thường
Regex cơ bản nhất chính là biểu thức bao gồm 1 ký tự thông thường, VD: a Nó sẽ so khớp với thực thể đầu tiên của ký tự đó trong chuỗi VD nếu có chuỗi: LazyDog is a boy, nó sẽ so khớp với ký tự a sau ký tự L Regex này cũng có thể so khớp với ký tự a thứ 2 nếu ta điều khiển regex engine tiếp tục tìm kiếm sau khi đã so khớp được 1 lần Cũng như vậy, regex dog sẽ so khớp với dog trong chuỗi LazyDog is not a dog Regex này bao gồm 1 sêri 3 ký tự thông thường Engine sẽ hiểu biểu thức này là: tìm d, theo sau bởi o, theo sau bởi g
Chú ý rằng regex engine mặc định phân biệt chữ hoa và chữ thường Dog không so khớp với dog
1.4.2.2 Kí tự đặc biệt
Vì ta cần làm nhiều công việc phức tạp hơn là tìm kiếm 1 đoạn văn bản, cho nên phải trưng dụng 1 vài ký tự để làm những nhiệm vụ đặc biệt Trong cú pháp regex được thảo luận ở đây, có
11 ký tự mang ý nghĩa đặc biệt: [ \ ^ $ | ? * + ( ) Chúng được gọi là các metacharacter
Nếu cần dùng các ký tự này với ý nghĩa thông thường, ta phải giải phóng nó bằng \ VD nếu cần so khớp 1+1=2, thì regex đúng sẽ là 1\+1=2 Chú ý rằng 1+1=2 cũng là regex đúng, nên sẽ không báo lỗi, nhưng nó sẽ không cho ta kết quả như mong muốn Regex 1+1=2 sẽ so khớp với
Trang 7111=2 trong chuỗi 123+111=234, vì dấu + ở đây mang ý nghĩa đặc biệt Nếu ta quên không giải phóng ký tự đặc biệt ở những chỗ nó không được phép đứng thì sẽ gặp lỗi VD: +1
Hầu hết các loại cú pháp regex đều coi { như 1 ký tự thông thường, trừ khi nó là 1 phần của toán tử nhắc lại (repetition operator), VD: {1, 3} Vì vậy ta không cần giải phóng ký tự này
Ta chỉ dùng \ để giải phóng các ký tự đặc biệt, còn các ký tự khác thì không nên, vì \ cũng là
1 ký tự đặc biệt \ khi kết hợp với 1 ký tự thông thường sẽ có ý nghĩa đặc biệt, VD: \d sẽ so khớp với 1 chữ số từ 0 - 9
Tất cả các loại cú pháp regex đều cho phép giải phóng 1 ký tự đặc biệt bằng \ Rất nhiều cú pháp khác còn hỗ trợ kiểu giải phóng \Q \E Tất cả các ký tự nằm trong cặp \Q và \E sẽ được coi như ký tự thông thường VD: \Q*\d+*\E sẽ so khớp với đoạn văn bản *\d+* Kiểu cú pháp này được hỗ trợ bởi JGsoft engine, Perl, PCRE
1.4.3 Tìm kiếm theo vị trí
1.4.3.1 Neo đầu và neo cuối chuỗi
Mỏ neo không so khớp với bất kỳ 1 ký tự nào Thay vào đó, chúng so khớp với 1 vị trí trước, sau hoặc giữa các ký tự Chúng được sử dụng để "neo" biểu thức regex vào 1 vị trí để so khớp Dấu ^ khớp với vị trí ngay trước ký tự đầu tiên trong chuỗi Áp dụng regex ^a cho chuỗi abc, ta
sẽ được a ^b sẽ không có kết quả khi so khớp với abc, vì b không khớp với ký tự ngay sau vị trí bắt đầu của chuỗi, vị trí được khớp bởi ^
Tương tự như trên, $ khớp với vị trí ngay sau ký tự cuối cùng của chuỗi c$ sẽ khớp với c trong abc, trong khi a$ không khớp
Khi sử dụng regex trong ngôn ngữ lập trình để kiểm định thông tin nhập vào từ người dùng,
sử dụng neo là rất quan trọng VD nếu ta dùng \d+ để kiểm tra xem người dùng có nhập vào 1 số nguyên hay không, kết quả trả về vẫn có thể là đúng thậm chí nếu người dùng nhập qsdf4ghjk, bởi \d+ khớp với 4 Regex đúng ở đây phải là ^\d+$ Bởi vì vị trí đầu chuỗi phải được khớp trước khi \d+ được khớp, và vị trí cuối chuỗi phải được khớp ngay sau đó, vì vậy chuỗi nhập vào nếu muốn khớp với ^\d+$ thì chỉ có thể là 1 chuỗi các chữ số (dấu + là toán tử nhắc lại, dùng để nhắc lại ký tự trước nó 1 hoặc nhiều lần)
Một chú ý khác là ta có thể dễ dàng mắc lỗi với ký tự trắng Trong ngôn ngữ Perl chẳng hạn, khi đọc vào 1 dòng từ 1 file text, ký tự line break cũng sẽ được lưu vào biến Do đó trước khi
Trang 8tiến hành kiểm định thông tin nhập vào, ta cần chặt bỏ các ký tự trắng đầu và cuối ^\s+ khớp với
ký tự trắng đầu và \s+$ khớp với ký tự trắng cuối
1.4.3.2 Sử dụng ^ và $ để neo đầu và cuối 1 dòng
Nếu chuỗi cần xử lý được viết trên nhiều dòng, như kiểu first line\nsecond line (\n là ký tự line break), ta có thể muốn làm việc với từng dòng hơn là với cả chuỗi Do đó, các regex engine được thảo luận trong loạt bài này còn có thêm chức năng mở rộng ý nghĩa của các mỏ neo ^ vừa
có thể khớp với vị trí đầu chuỗi (trước ký tự f trong VD trên), vừa có thể khớp với vị trí ngay sau mỗi line break (giữa \n và s) Cũng như vậy, $ vừa có thể khớp với vị trí cuối chuỗi (sau ký tự e cuối cùng), vừa có thể khớp với vị trí trước mỗi line break (giữa e và \n)
Trong ngôn ngữ lập trình, ta phải kích hoạt chức năng mở rộng này, được gọi là chế độ đa dòng (multi-line mode) VD trong Perl, ta làm việc này bằng cách thêm m vào sau đoạn mã regex, VD: m/^regex$/m;
1.4.3.3 Neo đầu và cuối chuỗi vĩnh cửu
\A sẽ chỉ khớp với vị trí đầu chuỗi, và \Z chỉ khớp với vị trí cuối chuỗi Chúng không bao giờ khớp ở vị trí các line break, thậm chí cả khi chế độ "multiline mode" được kích hoạt Điều này đúng cho tất cả các loại cú pháp regex được thảo luận trong bài này
1.4.4 Các lớp ký tự
Sử dụng lớp ký tự, ta sẽ khiến regex engine chỉ chọn ra 1 ký tự để so khớp Để sử dụng, ta đặt các ký tự cần so khớp vào 2 dấu [ và ] VD: để so khớp ký tự a hoặc e, ta dùng [ae] Như vậy biểu thức gr[ae]y sẽ khớp với gray hoặc grey
Lớp ký tự chỉ so khớp với 1 ký tự đơn Như vậy gr[ae]y sẽ không khớp với graay, graey,v.v Thứ tự các ký tự trong lớp không quan trọng Kết quả trả về luôn giống nhau
Để xác định 1 vùng ký tự trong lớp ký tự, ta sử dụng dấu - VD: [0-9] so khớp với 1 chữ số
từ 0 - 9 Có thể sử dụng nhiều vùng ký tự hoặc kết hợp vùng ký tự với ký tự đơn VD: [0-9a-fA-F] so khớp với 1 chữ số hệ 16, không phân biệt chữ hoa, thường [0-9a-fxA-FX] so khớp với 1 chữ số hệ 16 hoặc chữ cái X, không phân biệt chữ hoa, thường Cũng như trên, thứ tự các vùng không quan trọng
Trang 91.4.4.1 Lớp ký tự phủ định
Đặt dấu ^ sau [ trong lớp ký tự sẽ phủ định lớp ký tự đó Kết quả là lớp ký tự sẽ so khớp với bất kỳ ký tự nào không nằm trong lớp ký tự đó Lớp ký tự phủ định có thể so khớp với cả ký tự line break
Chú ý rằng lớp ký tự phủ định vẫn phải được so khớp với 1 ký tự VD: q[^u] không phải là
"q không theo sau bởi u" mà là "q theo sau bởi 1 ký tự không phải u" Vì vậy nó sẽ không so khớp với q trong chuỗi Iraq, và sẽ so khớp với q và space trong chuỗi Iraq is a country
1.4.4.2 Metacharacter trong lớp ký tự
Trong lớp ký tự, các ký tự mang ý nghĩa đặc biệt hay metacharacter chỉ bao gồm: ] \ ^ - Các metacharacter nói ở phần trước khi đặt trong lớp ký tự sẽ chỉ được coi như ký tự thông thường,
và do đó không cần phải giải phóng VD: để tìm ký tự * hoặc +, ta dùng [+*]
Để đặt ký tự \ vào trong lớp ký tự với nghĩa thông thường, cần giải phóng nó bằng 1 ký tự \ khác VD: [\\x] sẽ khơp với ký tự \ hoặc x Các ký tự ] ^ - nếu muốn dùng theo nghĩa thông thường cũng phải được giải phóng bằng \ hoặc đặt nó ở vị trí mà nó sẽ không có ý nghĩa đặc biệt
Ta nên dùng cách thứ 2 để biểu thức regex trông dễ nhìn hơn như sau: Với ^, đặt nó ở bất kỳ chỗ nào trừ vị trí ngay sau [ VD: [x^] sẽ khớp với x hoặc ^ Với ], đặt nó ngay sau [ hoặc [^ VD: []x] sẽ khớp với ] hoặc x [^]x] sẽ khớp với bất kỳ ký tự nào không phải là ] hoặc x
Với -, đặt nó ngay sau [ hoặc [^ , hoặc ngay trước ] VD: cả [-x] và [x-] đều so khớp với - hoặc x
Có thể sử dụng tất cả các ký tự không in được trong lớp ký tự giống như dùng chúng ngoài lớp ký tự VD: [$\u20AC] sẽ khớp với $ hoặc ký tự đồng euro (với giả định cú pháp regex đang dùng hỗ trợ unicode)
JGsoft engine, Perl và PCRE còn hỗ trợ kiểu \Q \E trong lớp ký tự để giải phóng 1 chuỗi ký
tự VD: [\Q[-]\E] sẽ khớp với [ hoặc - hoặc ]
Cú pháp regex của POSIX lại xử lý \ trong lớp ký tự như 1 ký tự thông thường Đồng nghĩa với việc ta không thể dùng \ để giải phóng ] ^ - Để làm việc này ta chỉ còn cách đặt chúng vào các vị trí như trình bày ở trên Ngoài ra điều này cũng đồng nghĩa với việc các cú pháp tắt (shorthand, VD: \d) không còn hiệu lực
Trang 101.4.4.3 Lớp ký tự viết tắt (Shorthand Character Classes)
\d là dạng tắt của [0-9]
\w được gọi là "ký tự từ" (word character) Chính xác những ký tự nào được khớp với nó thay đổi tuỳ theo mỗi loại cú pháp regex Trong tất cả các loại cú pháp, nó sẽ bao gồm [A-Za-z] Trong hầu hết các loại cú pháp, nó cũng bao gồm cả dấu _ và chữ số
\s được gọi là "ký tự trắng" (whitespace character) Nó khớp với ký tự nào thì cũng tùy thuộc vào từng loại cú pháp Trong kiểu cú pháp thảo luận ở đây, nó bao gồm [\t] Nghĩa là \s sẽ khớp với space hoặc tab Trong hầu hết cú pháp , nó cũng bao gồm cả ký tự carriage return hoặc line feed, nghĩa là [\t\r\n] Một số cú pháp khác lại bao gồm thêm cả các ký tự không in được hiếm khi dùng như vertical tab hoặc form feed
Các lớp ký tự viết tắt có thể được dùng cả trong lẫn ngoài cặp [] VD: \s\d khớp với 1 ký tự trắng theo sau bởi 1 chữ số [\s\d] khớp với 1 ký tự đơn là 1 ký tự trắng hoặc 1 chữ số Khi áp dụng vào chuỗi 1 + 2 = 3, regex thứ 1 sẽ khớp với 2 (space và 2), trong khi regex thứ 2 sẽ khớp với 1 [\da-fA-F] khớp với 1 chữ số hệ 16, giống như [0-9a-fA-F]
1.4.4.4 Lớp ký tự viết tắt phủ định (Negated Shorthand Character Classes)
\D tương đương [^\d]
\W tương đương [^\w]
\S tương đương [^\s]
1.4.5 Biểu thức lặp theo độ dài
Biểu thức chính quy sử dụng các ký hiệu + * ? để thể hiện việc lặp các ký tự theo độ dài
- Ký tự * : mang ý nghĩa lặp lại ký tự, nhóm ký tự trước nó từ 0 đến n lần
- Ký tự + : mang ý nghĩa lặp lại ký tự, nhóm ký tựn trước nó từ 1 đến n lần
- Ký tự ? : mang ý nghĩa lặp lại ký tự, nhóm ký tự trước nó 0 hoặc 1 lần
- Ngoài ra biểu thức chính quy còn cho phép quy định biểu thức lặp lại chính xác số lần
- {x}: Mang ý nghĩa lặp lại ký tự, nhóm ký tự trước nó đúng x lần
- {x,y}: Mang ý nghĩa lặp lại ký tự, nhóm ký tự trước nó từ x đến y lần
- {x,}: Mang ý nghĩa lặp lại ký tự, nhóm ký tự trước nó lớn hơn bằng x lần