Tải bản đầy đủ (.doc) (12 trang)

Ngôn ngữ Perl-Chương 07-Biểu thức chính quy

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (181.91 KB, 12 trang )

Learning Perl - Chương 7: Biểu thức chính qui
1. Nhập vào từ STDIN
2. Nhập vào từ toán tử hình thoi
3. Đưa ra STDOUT
4. Bài tập
7.1 Khái niệm về biểu thức chính qui
Biểu thức chính qui là một khuôn mẫu - một tiêu bản - để được sánh với một xâu. Việc sánh
một biểu thức chính qui với một xâu thì hoặc thành công hoặc thất bại. Đôi khi, sự thành công
hay thất bại này có thể là tất cả những gì bạn quan tâm tới. Vào lúc khác, bạn sẽ muốn lấy một
khuôn mẫu đã sánh đúng và thay thế nó bằng một xâu khác, một phần trong đó có thể phụ
thuộc đích xác vào cách thức và nơi chốn mà biểu thức chính qui được sánh đúng. Biểu thức
chính qui thường được nhiều chương trình UNIX dùng tới, như grep, sed, awk, ed, vi, emacs
và thậm chí cả nhiều shell script nữa. mỗi chương trình đều có một tập các kí tự tiêu bản khác
nhau. Perl là một siêu tệp ngữ nghĩa cho tất cả những công cụ này - bất kì biểu thức chính qui
nào mà có thể được viết trong một trong những công cụ UNIX này thì cũng đều có thể được
viết trong Perl, nhưng không nhất thiết dùng hệt các kí tự đó.
7.2 Cách dùng đơn giản về biểu thức chính qui
Nếu chúng ta tìm tất cả các dòng của một tệp có chứa xâu abc, thì ta có thể dùng lệnh grep:
grep abc sonefile > result
Trong trường hợp này, abc là biểu thức chính qui mà lệnh grep lấy để kiểm tra cho từng dòng
đưa vào. Những dòng so sánh đúng sẽ được chuyển ra lối ra chuẩn (ở đây, kết quả sẽ được ghi
vào tệp result).
Trong Perl, ta có thể nói về xâu abc như biểu thức chính qui bằng việc bao xâu này trong hai
dấu sổ chéo:
if (/abc/) {
print "$_";
}
Nhưng cái gì được kiểm tra so với biểu thức chính qui abc trong trường hợp này? Tại sao biến
$_ lại có mặt ở đây? Khi một biểu thức chính qui được bao trong hai dấu sổ chéo (như trên), thì
biến $_ sẽ được kiểm tra theo biểu thức chính qui đó. Nếu biểu thức chính qui so sánh đúng,
thì toán tử so sánh sẽ trả về giá trị đúng; ngược lại nó trả về giá trị sai.


Trong thí dụ này, biến $_ được giả sử có chứa một dòng văn bản nào đó, và được in ra nếu
dòng này có chứa các kí tự abc đâu đó bên trong dòng - tương tự như lệnh grep ở trên. Không
giống như chỉ lệnh grep, vốn vận hành trên tất cả các dòng của tệp, đoạn chương trình Perl này
chỉ nhìn vào có một dòng thôi. Để làm việc trên tất cả các dòng, ta cần thêm vào một chu trình,
như trong:
while (<>) {
if (/abc/) {
print "$_";
}
}
Điều gì sẽ xảy ra nêu như ta không biết được số lượng các ký tự b giữa a và c? Tức là, điều gì
sẽ xảy ra nếu ta muốn in dòng có chứa một a và theo sau nó là không hay nhiều b, rồi theo sau
nữa là một c? Với grep, ta phải nói:
grep "ab*c" somefile > result
Trong Perl, chúng ta có thể làm tương tự như sau:
while (<>) {
if (/ab*c/) {
print “$_”;
}
}
Cũng hệt như grep, điều này có nghĩa là một a theo sau bởi không hay nhiều b, theo sau là c.
Chúng ta sẽ xem xét nhiều tuỳ chọn khác về toán tử đối sánh trong mục "Nói thêm về toán tử
đối sánh", ở cuối chương này, sau khi ta đã nói về tất cả các loại biểu thức chính qui.
Một toán tử biểu thức chính qui nữa là toán tử thay thế, làm việc thay thế một phần của xâu mà
sánh đúng biểu thức chính qui bằng một xâu khác. Toán tử thay thế giống như chỉ lệnh s trong
sed, bao gồm một chữ s, một sổ chéo, một biểu thức chính qui, một sổ chéo, một xâu thay thế,
và một sổ chéo cuối cùng, trông tựa như thế này: s/ab*c/def/;
Xâu (trong trường hợp này là biến $_) được đem ra đối sánh với biểu thức chính qui (ab*c).
Nếu việc đối sánh thành công, thì phần của xâu sánh đúng sẽ bị loại ra và được thay thế bằng
xâu thay thế (def). Nếu việc đối sánh không thành công thì chẳng có gì xảy ra cả.

Như với toán tử đối sánh, ta sẽ còn xem xét lại vô số các tuỳ chọn về toán tử thay thế dưới đây,
trong mục "Thay thế".
7.3 Khuôn mẫu
Một biểu thức chính qui là một khuôn mẫu. Một số phần của khuôn mẫu sánh đúng chỉ các kí
tự trong xâu thuộc kiểu đặc biệt. Những phần khác của khuôn mẫu sánh đúng cho đa kí tự.
Trước hết, ta sẽ xem các khuôn mẫu một kí tự, rồi đến các khuôn mẫu đa kí tự.
• 7.3.1 Khuôn mẫu một kí tự
Kí tự sánh mẫu đơn giản nhất và thông dụng nhất trong các biểu thức chính qui là một kí tự
sánh với chính nó. Nói cách khác, đặt một chữ a vào trong biểu thức chính qui đòi hỏi một chữ
tương ứng a trong xâu.
Kí tự sánh mẫu thông dụng nhất tiếp đó là dấu chấm (.). Dấu chấm đối sánh bất kì kí tự riêng lẻ
nào ngoại trừ dấu xuống dòng mới (\n). Chẳng hạn, khuôn mẫu /a./ đối sánh bất kì dãy hai kí tự
nào bắt đầu bằng a và không phải là a\n.
Lớp kí tự sánh mẫu được biểu diễn bởi cặp dấu ngoặc vuông mở và đóng, và một danh sách
các kí tự nằm giữa hai dấu ngoặc này. Một và chỉ một trong các kí tự này phải hiện diện tại
phần tương ứng của xâu cần sánh mẫu. Chẳng hạn, /[abcde]/ sẽ sánh đúng với bất kì một trong
năm chữ đầu tiên của bảng chữ thường, trong khi /[aeiouAEIOU]/ lại sánh với bất kì năm
nguyên âm hoặc chữ thường hoặcchữ hoa. Nếu bạn muốn đặt dấu ngoặc vuông phải (]) vào
danh sách thì hãy đặt một sổ chéo ngược ở trước nó (ví dụ \]), hay đặt nó như kí tự đầu tiên bên
trong danh sách.
Phạm vi của các kí tự (như a tới z) có thể được viết tắt bằng việc chỉ ra những điểm cuối của
phạm vi được tách biệt bởi dấu gạch ngang (-); để có được hằng kí hiệu gạch ngang, bạn hãy
đặt trước dấu gạch ngang một sổ chéo ngược. Sau đây là một số thí dụ khác:
[0123456789] # sánh với mọi chữ số
[0-9] # tương tự nhưu trên
[0-9\-] # sánh 0-9 hay dấu trừ
[a-z0-9] # sánh bất kì chữ thường hay số nào
[a-zA-Z0-9_] # sánh bất kì chữ, số hay dấu gạch dưới
Cũng có lớp kí tự bị phủ định, cũng là cùng lớp kí tự, nhưng có thêm dấu mũ (^) đằng trước, đi
ngay sau dấu ngoặc trái. Lớp kí tự này đối sánh với bất kì kí tự đơn nào không trong danh sách.

Chẳng hạn:
[^0-9] # sánh với bất kì kí tự nào không phải là chữ số
[^aeiouyAEIOUY] # sánh với bất kì kí tự nào không nguyên âm
[^\^] # sánh với bất kỳ ký tự nào không phải là dấu mũ
Để tiện cho bạn, đã có định nghĩa sẵn một số lớp ký tự chung, như được mô tả trong Bảng 7-1.
Viết tắt Nghĩa
\d (số) [0-9]
\D (phủ định của \d) [^0-9]
\w (từ) [a-zA-Z0-9_]
\W (phủ định của \w) [^a-zA-Z0-9_]
\s (khoảng trắng) [ \r\t\n\f]
\S (phủ định của \s) [^ \r\t\n\f]
Khuôn mẫu \d sánh với "số". Khuôn mẫu \w sánh với "kí tự từ", mặc dầu điều thực sự sánh
đúng là bất kì cái gì hợp lệ trong tên biến Perl. Khuôn mẫu \s sánh với "dấu cách" (khoảng
trắng), ở đây được xác định như ký tự space, về đầu dòng (ít dùng trong UNIX), tab, xuống
dòng (dấu dòng mới của UNIX), và kéo giấy. Các bảng chữ hoa sánh đúng với cái đối lập cho
những lớp này.
• 7.3.2 Khuôn mẫu nhóm
Sức mạnh thực sự của biểu thức chính qui là khi bạn có thể nói "một hay nhiều những thứ này"
hay "cho tới năm thứ này". Ta hãy nói về cách thực hiện điều này.
• 7.3.2.1 Dãy
Khuôn mẫu nhóm đầu tiên (và có lẽ kém hiển nhiên nhất) là dãy. Điều này có nghĩa là abc sánh
đúng với một a theo sau là b, theo sau là c. Nó dường như đơn giản, nhưng tôi cứ đặt tên cho
nó để tôi có thể nói về nó sau này.
• 7.3.2.2 Bội
Chúng ta đã thấy dấu sao (*) như một khuôn mẫu nhóm. Dấu * chỉ ra rằng "không hay nhiều"
kí tự (hay lớp kí tự) đứng ngay trước nó.
Hai khuôn mẫu nhóm khác làm việc giống thế là dấu cộng (+), nghĩa là "một hay nhiều" kí tự
đứng ngay trước, và dấu hỏi (?), nghĩa là "không hay một" kí tự ngay trước. Chẳng hạn, biểu
thức chính qui /fo+ba?r/ sánh đúng cho một f theo sau là một hay nhiều o, theo sau là a, b và

tuỳ chọn a, theo sau là một r.
Trong tất cả ba khuôn mẫu nhóm này, các khuôn mẫu đều tham lam. Nếu một khuôn mẫu như
vậy có cơ hội sánh đúng giữa năm và mười kí tự thì nó sẽ lọc ra xâu mười kí tự mỗi lúc. Chẳng
hạn:
$_ = "jerry xxxxxxxxxx tom";
s/x*/boom/;
Bao giờ cũng thay tất cả các x liên tiếp bằng boom (kết quả là jerry boom tom), thay vì chỉ thay
thế cho một hay hai x, cho dù một tập x ngắn hơn cũng sánh được cho cùng biểu thức chính
qui.
Nếu bạn cần nói "năm tới mười" x, thì bạn có thể xoay xở bằng cách đặt năm x theo sau bởi
năm x nữa đi liền sau dấu chấm hỏi. Nhưng làm thế trông xấu, mà cũng chẳng làm việc tốt
lắm. Thay vì vậy, có một cách dễ hơn: số bội tổng quát. Số bội tổng quát bao gồm một cặp dấu
ngoặc nhọn với một hay hai số bên trong, ví dụ /x{5,10}/. Giống như ba số bội khác, kí tự
đứng ngay trước (trong trường hợp này là chữ "x") phải được tìm thấy bên trong số lần lặp đã
chỉ ra (năm đến mười ở đây).
Nếu bạn bỏ đi con số thứ hai, như trong /x{5,}/, thì điều này có nghĩa là "nhiều hay hơn nữa"
(năm hay nhiều hơn trong trường hợp này), và nếu bạn bỏ nốt dấu phẩy, như trong /x{5}/, thì
điều đó có nghĩa là "đúng con số này" (đúng năm x). Để được 5 x hay ít hơn, bạn phải đặt số
không vào, như trong /x{0,5}/.
Vậy, biểu thức chính qui /a.{5}b/ sánh đúng cho kí tự a được tách với ký tự b bởi bất kì năm kí
tự khác kí tự xuống dòng mới (nhớ lại rằng dấu chấm sánh với bất kì kí tự khác dấu xuống
dòng, và chúng ta sánh với năm kí tự như thế ở đây). Năm kí tự này không cần phải như nhau
(chúng ta sẽ biết cách để buộc chúng là như nhau trong mục tiếp).
Ta có thể loại bỏ hẳn hoàn toàn *, +, và ?, vì chúng hoàn toàn tương đương với {0,}, {1,}, và
{0,1}. Nhưng dễ dàng hơn vẫn là gõ một kí tự ngắt tương đương, mà cũng quen thuộc hơn.
Nếu có hai số bội trong một biểu thức, thì qui tắc tăng được tăng lên với "bên trái nhất là tăng
lên nhất". Chẳng hạn:
$_ = "a xxx c xxxx c xxx d";
/a.*c.*d/;
Trong trường hợp này, ".*" thứ nhất trong biểu thức chính qui sánh với tất cả các kí tự cho tới c

thứ hai, cho dù việc sánh đúng chỉ với các kí tự cho tới c đầu tiên vẫn cho phép toàn bộ biểu
thức chính qui được sánh. Điều này không tạo ra khác biệt gì (khuôn mẫu sẽ sánh theo cả hai
cách), nhưng sau này khi chúng ta có thể nhìn vào các bộ phận của biểu thức chính qui mà
được sánh, thì sẽ có đôi chút vấn đề.
Điều gì xảy ra nếu biểu thức xâu và chính qui hơi bị thay đổi đi, chẳng hạn như:
$_ = “a xxx ce xxxxxxx ci xxx d”;
/a.*ce.*d/;
Trong trường hợp này, nếu ".*" sánh với phần lớn các kí tự có thể trước c tiếp, thì kí tự biểu
thức chính qui tiếp (e) sẽ không sánh với kí tự tiếp của xâu (i). Trong trường hợp này, ta thu
được việc lần ngược tự động - số bội bị tháo ra và thử lại, dừng lại tại chỗ nào đó phía trước
(trong trường hợp này, tại c trước, tiếp sau là (e)* . Một biểu thức chính qui phức tạp có thể
bao gồm nhiều mức lần ngược như vậy, dẫn tới thời gian thực hiện lâu.
• 7.3.2.3 Dấu ngoặc tròn như bộ nhớ
Một toán tử nhóm khác là cặp mở và đóng ngoặc tròn quanh bất kì phần khuôn mẫu nào. Điều
này không làm thay đổi liệu khuôn mẫu có sánh đúng hay không, nhưng thay vì thế lại làm cho
một phần của xâu được khuôn mẫu sánh đúng sẽ được ghi nhớ, để cho nó có thể được tham
khảo tới về sau. Vậy chẳng hạn, (a) vẫn sánh với a, còn ([a-z]) thì vẫn sánh với bất kì chữ
thường nào.
Để nhớ lại một phần đã ghi nhớ của một xâu, bạn phải đưa vào một dáu sổ chéo ngược theo
sau bởi một số nguyên. Kết cấu khuôn mẫu này biểu thị cho cùng dãy các kí tự được sánh
trước đây trong cặp dấu ngoặc tròn cùng số (đếm từ một). Chẳng hạn:
/jerry(.)tom\1/;
Sẽ sánh một xâu có chứa jerry, tiếp theo là một kí khác dấu xuống dòng, tiếp nữa là tom, rồi
tiếp bởi cùng một kí tự đó. Vậy, nó sánh với jerryxtomx, nhưng không sánh với jerryxtomy.
Bạn hãy so sánh điều đó với /jerry.tom./ trong đó hai kí tự không xác định có thể là một, hay
khác nhau - cũng chẳng thành vấn đề gì; /jerry.tom./ sẽ sánh với cả jerryxtomx và jerryxtomy.
Số 1 đến từ đâu vậy? Nó có nghĩa là phần biểu thức chính qui nằm trong dấu ngoặc đầu tiên.
Nếu có nhiều phần như thế, thì phần thứ hai (đếm các dấu ngặc trái từ trái sang phải) sẽ được
tham khảo tới là \2, phần thứ ba là \3, và cứ thế. Chẳng hạn:
/a(.)b(.)c\2d\1/;

Sẽ sánh với một a, một kí tự (gọi nó là #1), một b, một kí tự khác (gọi nó là #2), một c, kí tự
#2, một d, và kí tự #1 (cho nên nó sánh với axbycydx chẳng hạn).
Phần được tham khảo tới có thể nhiều hơn một kí tự. Chẳng hạn:
/a(.*)b\1c/;
Sẽ sánh với một a, theo sau bởi một số bất kì kí tự nào (thậm chí không), theo sau bởi b, theo
sau bởi cùng dãy kí tự đó, theo sau bởi c. Vậy, nó sẽ sánh với aFREDnFREDc, hay thậm chí
abc, nhưng không sánh aXXbXXXc.
Một cách dùng khác của phần được nhớ của biểu thức chính qui là trong xâu thay thế của chỉ
lệnh thay thế. Kết cấu kiểu \1 vẫn giữ giá trị của chúng trong xâu thay thế, và có thể được tham
khảo tới để xây dựng xâu, như trong:
$_ = "a xxx b yyy c zzz d";
s/b(.*)c/d\1e/;
sẽ thay thế b và c bằng d và e, vẫn giữ lại phần ở giữa.
• 7.3.2.4 Thay phiên
Một kết cấu nhóm khác là thay phiên, như trong a|b|c. Điều này có nghĩa là sánh đúng một
trong các khả năng (a hoặc b hoặc c trong trường hợp này). Điều này vẫn có tác dụng ngay cả

×