Lessons VII : Khái niệm về biểu thức chính qui

Perl

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ả khi các thay phiên có nhiều kí tự, như trong /song|blue/, sẽ sánh hoặc song hoặc blue. (với những thay phiên đơn giản, tốt hơn cả là bạn có thể bỏ lớp kí tự như /[abc]/).

Điều gì xảy ra nếu ta muốn sánh songbird hay bluebird? Ta có thể viết /songbird|bluebird/, nhưng phần bird đó không nên có đó hai lần; tốt hơn ta có thể ghi /(song|blue)bird/. Trong thực tế, cũng có cách ra, nhưng ta phải nói tới thứ tự ưu tiên cho các khuôn mẫu nhóm, sẽ được đề cập tới trong mục “Thứ tự ưu tiên” dưới đây.

7.3.3 Khuôn mẫu neo

Bốn kí pháp đặc biệt đóng neo cho một khuôn mẫu. Thông thường, khi một khuôn mẫu được sánh với xâu thì sự bắt đầu của khuôn mẫu đó được rê đi trong toàn bộ xâu từ trái sang phải, sánh với cơ hội có thể đầu tiên. Neo cũng cho phép bạn đảm bảo rằng các phần của dòng khuôn mẫu sắp thẳng với những phần đặc biệt của xâu.

Cặp neo thứ nhất đòi hỏi rằng một phần đặc biệt của việc đối sánh phải được định vị tại biên giới từ hay không tại biên giới từ. Neo \b yêu cầu một biên giới từ tại điểm đã chỉ ra cho khuôn mẫu đối sánh. Biên giới từ là nơi ở giữa các kí tự sánh với \w và \W, hay giữa các kí tự sánh với \w và chỗ bắt đầu hay kết thúc của xâu. Chú ý rằng điều này ít phải xử lí đối với tiếng Anh và phải làm nhiều đối với các kí hiệu C, nhưng điều đó cũng gần thôi khi ta đạt tới. Chẳng hạn:

/fred\b/;   # sánh fred, nhưng không frederick

/\bwiz/;    # sánh wiz và wizard, nhưng không qwiz

/\bFred\b/; # sánh Fred nhưng không Frederick hay alFred

/abc\bdef/; # không bao giờ sánh (không thể có cận ở đây)

/\bFred\B/; # sánh “Frederick” nhưng không “Fred Flintstonee”

Hai neo nữa yêu cầu rằng một phần đặc biệt của khuôn mẫu phải đi ngay sau cuối xâu. Dấu mũ (^) sánh với điểm bắt đầu của xâu nếu nó đang ở một vị trí tạo ra nghĩa để đối sánh tại chỗ bắt đầu của xâu. Chẳng hạn, ^a sánh một a nếu và chỉ nếu a là kí tự đầu tiên của xâu. Tuy nhiên, ^a cũng sánh với hai kí tự a và ^ ở bất kì đâu trong xâu, nói cách khác, dấu mũ đã mất ý nghĩa đặc biệt của nó. Nếu bạn cần dấu mũ là một hằng kí hiệu dấu mũ ngay tại chỗ bắt đầu, thì hãy đặt một dấu sổ chéo ngược phía trước nó.

Dấu $ cũng giống như ^, neo lại khuôn mẫu, nhưng tại cuối của xâu, không phải bắt đầu. nói cách khác, c$ sánh với một c chỉ nếu nó xuất hiện tại cuối xâu. Dấu $ ở bất kì nơi đâu khác trong khuôn mẫu có lẽ vẫn cứ được diễn giải như cách hiểu giá trị vô hướng, cho nên bạn gần như bao giờ cũng phải dùng dấu sổ chéo ngược để đối sánh một dấu hiệu đó là hàng kí hiệu trong xâu.

7.3.4 Thứ tự ưu tiên

Vậy điều gì xảy ra khi ta lấy a|b* cùng nhau? Liệu đây là a hay b một số lần bất kì hay chỉ một a hay nhiều b? Được rồi, cũng giống như các toán tử có số ưu tiên, các khuôn mẫu bỏ neo và gộp nhóm cũng có độ ưu tiên. Độ ưu tiên của khuôn mẫu từ cao xuống thấp nhất được cho trong Bảng 7-2.

Bảng 7-2: Số ưu tiên toán tử gộp nhóm biểu thức chính qui (cao nhất xuống thấp nhất)

Tên Biểu diễn

Dấu ngoặc tròn ( )

Số bội ? + * {m,n}

Tuần tự và bỏ neo abc ^ $ \B \b

Thay phiên |

Theo bảng này, * có độ ưu tiên cao hơn |. Cho nên /a|b*/ được diễn giải như một a, hay số bất kì b.

Điều gì xảy ra nếu ta muốn một nghĩa khác, như trong “bất kì số a hay b nào”? Chúng ta đơn thuần chỉ ném vào một cặp dấu ngoặc. Trong trường hợp này, bạn hãy bao phần của biểu thức mà toán tử * cần áp dụng, vào bên trong các dấu ngoặc, và ta sẽ được nó, như (a|b)*. Nếu bạn muốn làm rõ ràng biểu thức thứ nhất, thì bạn có thể đóng dấu ngoặc (dư thừa) nó với a|(b*).

Khi bạn dùng dấu ngoặc để tác động tới số ưu tiên thì chúng cũng đặt lẫy bộ nhớ, như đã chỉ ra trước đây trong chương này. Tức là tập các dấu ngoặc này sẽ cần được đếm khi bạn muốn nói tới một cái gì đó là \2, \3 hay bất kì cái gì. Nếu bạn muốn dùng dấu ngoặc trong trường hợp này nhưng không muốn lưu lại trong bộ nhớ (dạng \1 \2 \3) bạn hãy dùng (?: ) thay cho ( ).

Sau đây là một số thí dụ khác về biểu thức chính qui, và tác động của dấu ngoặc:

abc*            # sánh với ab, abc, abcc, abccc, abcccc vân vân

(abc)*          # sánh với “”, abc, abcabc, abcabcabc vân vân

^x|y            # sánh x tại đầu dòng, hay y ở bất kì đâu

^(x|y)          # sánh hoặc với x hoặc với y tại đầu dòng

a|bc|d          # a hoặc bc hoặc d

(a|b)(c|d)      # ac, ad, bc hoặc bd

(song|blue)bird # songbird hay bluebird

7.4 Thêm về toán tử đối sánh

Ta đã nhìn vào cách dùng đơn giản nhất của toán tử đối sánh (một biểu thức chính qui được bao trong sổ chéo). Bây giờ ta hãy nhìn vào vô vàn cách làm cho toán tử này làm được điều gì đó hơi khác hơn.

7.4.1 Chọn một mục tiêu khác (toán tử =~)

Đôi khi xâu bạn muốn sánh với khuôn mẫu lại không bên trong biến $_, và đó sẽ là sắc thái để đặt nó ở đó (có lẽ bạn đã có một giá trị trong $_ mà bạn rất thích và không muốn thay đổi nó). Không hề gì. Toán tử =~ sẽ giúp chúng ta ở đây. Toán tử này nhận một toán tử biểu thức chính qui ở vế bên phải, rồi thay đổi đối tượng của toán tử này thành một cái gì đó bên cạnh biến $_ – có nghĩa là một giá trị nào đó có tên bên vế trái của toán tử này. Nó trông tựa như thế này:

$a = “hello world”;

$a =~ /^he/;

$a =~ /(.)\l/;

if ($a =~ /(.)\1/) {

}

Mục tiêu của toán tử =~ có thể là bất kì biểu thức nào cho một giá trị xâu vô hướng nào đó. Chẳng hạn, <STDIN> cho một giá trị xâu vô hướng khi được dùng trong hoàn cảnh vô hướng, cho nên chúng ta có thể tổ hợp điều này với toán tử =~ và một toán tử sánh biểu thức chính qui để được một kiểm tra gọn gàng về cái vào đặc biệt, như trong:

print “any more?”;

if (<STDIN> =~ /^[yY]/) { # có phải bắt đầu bằng một chữ y không?

print “So what is it? “;

<STDIN>; # bỏ qua 1 dòng

print “Sorry, I’m unable to do that.\n”;

}

Trong trường hợp này, toán tử <STDIN> đọc 1 dòng từ lồi vào chuẩn (bàn phím), mà rồi ngay lập tức được dùng như xâu đem sánh với khuôn mẫu ^[yY]. Lưu ý rằng bạn chưa bao giờ cất giữ cái vào vào một biến, cho nên nếu bạn muốn sánh cái vào với mẫu khác, hay có thể cho hiện lại dữ liệu trong một thông báo lỗi thì bạn khong gặp may rồi. Nhưng dạng này thường hay đến đúng lúc.

7.4.2 Bỏ qua chữ hoa thường

Trong thí dụ trước, tôi đã dùng [yY] để đối sánh hoặc chữ Y hoa hoặc y thường. Với những xâu rất ngắn như y hay jerry thì điều này là dễ dàng, như [fF] [oO] [oO]. Nhưng điều gì xẩy ra nếu xâu tôi muốn sánh lại là từ “procedure” trong hoặc chữ thường hoặc chữ hoa?

Trong một số phiên bản của grep, cờ -i chỉ ra “bỏ qua hoa thường”. Perl cũng có tuỳ chọn như vậy. Bạn chỉ ra tuỳ chọn bỏ qua hoa thường bằng cách thêm vào chữ i thường vào sau sổ chéo đóng, như trong /somepattern/i. Điều này nói lên rằng các chữ của khuôn mẫu này sẽ sánh với các chữ trong xâu trong cả chữ hoa lẫn thường. Chẳng hạn, để sánh từ “procedure” trong cả hoa lẫn thường tại đầu dòng, bạn hãy dùng /^procedure/i.

Bây giờ thí dụ trước của ta trong giống thế này:

print “anu more?”;

if (<STDIN> =~ /^y/i) {

# có! xử lí cho nó

}

7.4.3 Dùng một định biên khác

Nếu bạn đang tìm kiếm một xâu với một biểu thức chính qui có chứa kí tự sổ chéo (/), thì bạn phải đặt trước mỗi sổ chéo một sổ chéo ngược (\). Chẳng hạn, bạn có thể tìm một xâu bắt đầu bằng /usr/etc tựa như thế này:

$path = <STDIN>; # đọc một tên đường dẫn

if ($path =~ /^\/usr\/etc/) {

# bắt đầu với /usr/etc …

}

Như bạn có thể thấy, tổ hợp sổ chéo ngược-sổ chéo làm cho nó trông giống như có một thung lũng nhỏ giữa hai mẩu văn bản. Làm điều này cho nhiều sổ chéo có thể gây cồng kềnh, cho nên Perl cho phép bạn xác định một kí tự định biên khác. Chỉ cần đặt trước bất kì kí tự phi chữ-số nào (định biên do bạn chọn) với một m, rồi liệt kê khuôn mẫu của bạn theo sau bởi một kí tự định biên y hệt thế nữa, là bạn đã hoàn thành, như trong:

/^\/usr/etc/ # dùng định biên sổ chéo chuẩn

m@^/usr/etc@ # dùng @ làm định biên

m#^/usr/etc# # dùng # làm định biên (sở thích của tôi)

Bạn có thể thậm chí dùng cả sổ chéo lần nữa nếu bạn cứ muốn, như trong m/jerry/, cho nên toán tử sánh biểu thức chính qui thông thường thực sự là toán tử m, tuy nhiên, m là tuỳ chọn, nếu bạn chọn sổ chéo làm định biên.

7.4.4 Dùng xen lẫn biến

Một biểu thức chính qui là được xen lẫn biến trước khi nó được xem xét cho các kí tự đặc biệt khác. Do đó, bạn có thể xây dựng một biểu thức chính qui từ các xâu được tính toán thay vì chỉ là hằng ký hiệu. Chẳng hạn:

$what = “bird”;

$sentence = “Every good bird does fly.”;

if ($sentence =~ /\b$what\b/) {

print “$sentence\n”;

}

Tại đây chúng ta đã xây dựng một cách có hiệu quả toán tử biểu thức chính qui /\bbird\b/ bằng việc dùng một tham khảo biến.

Sau đây là một thí dụ có hơi phức tạp hơn:

$sentence = “Every good bird does fly.”;

print “What should I find? “;

$what = <STDIN>;

chop ($what);

if ($sentence =~ /$what/) { # tìm thấy nó!

print “Yes, saw $what in $sentence.\n”;

} else {

print “No…nothing!.\n”;

}

Nếu bạn đưa vào bird, thì nó được tìm ra. Nếu bạn đưa vào scream nó sẽ không tìm thấy. Nếu bạn đưa vào [bw]ird, điều ấy cũng được tìm ra, chỉ ra rằng các kí tự đối sánh khuôn mẫu biểu thức chính qui quả thực là vẫn có ý nghĩa. Tôi sẽ chỉ ra cho bạn trong phần “Thay thế” dưới đây về cách thay đổi xâu để cho các ký tự đối sánh khuôn mẫu chính qui được tắt đi.

7.4.5 Biến chỉ đọc đặc biệt

Sau khi đối sánh khuôn mẫu thành công, các biến $1, $2, $3 vân vân sẽ được đặt cho cùng giá trị là \1, \2, \3 tương ứng. Bạn có thể dùng điều này để nhìn vào một phần của việc đối sánh trong đoạn chương trình sau. Chẳng hạn:

$_ = “đây là phép kiểm tra”;

/(\W+)\W+(\W+)/; # đối sánh hai từ đầu

# $1 bây giờ là “đây” còn $2 bây giờ là “là”

Bạn cũng có thể thu được cùng các giá trị ($1, $2, $3 vân vân) bằng việc đặt đối sánh trong hoàn cảnh mảng. Kết quả là một danh sách các giá trị mà sẽ được đặt cho $1 cho tới số các vật được ghi nhớ, nhưng chỉ nếu biểu thức chính qui sánh đúng. Ta hãy lấy lại thí dụ trước theo cách khác

$_ = “This is a test”;

/(\W+)\W+(\W+)/; # đối sánh hai từ ở hai đầu

# $1 bây giờ là “this” còn $2 bây giờ là “test”

Lưu ý rằng các biến $1 và $2 vẫn không bị thay đổi.

Các biến chỉ đọc được xác định trước còn bao gồm $&, là một phần của xâu sánh đúng với biểu thức chính qui; $`, là một phần của xâu trước phần sánh đúng; còn $’ là phần của xâu sau phần sánh đúng. Chẳng hạn:

$_ = “this is sample string”;

/sa.*le/; # sánh “sample” bên trong xâu

# $` bây giờ là “this is a ”

# $& bây giờ là “sample”

# $’ bây giờ là ” string”

Vì tất cả những biến này đều được đặt lại cho từng lần sánh thành công cho nên bạn nên cất giữ các giá trị trong các biến vô hướng khác nếu bạn cần các giá trị đó về sau trong chương trình.

7.5 Thay thế

Chúng ta đã nói về dạng đơn giản nhất của toán tử thay thế: s/old-regex/new-string/. Bây giờ là lúc nói tới vài biến thể của toán tử này.

Nếu bạn muốn việc thay thế vận hành trên tất cả các đối sánh có thể thay vì chỉ việc đối sánh đầu tiên thì hãy viết thêm g vào toán tử này, như trong:

$_ = “foot fool buffon”;

s/foo/bar/g; # $_ bây giờ là “bart barl buffon”

Xâu thay thế có biến xen vào, cho phép bạn xác định xâu thay thế vào lúc chạy:

$_ = “hello, world”;

$new = “goodbye”;

s/hello/$new/; # thay thế hello bằng goodbye

Các kí tự khuôn mẫu trong biểu thức chính qui cho phép các khuôn mẫu được đối sánh, thay vì chỉ là các kí tự cố định:

$_ = “this is a test”;

s/(\w+)/<$1>/g; # $_ bây giờ là “<this> <is> <a> <test>”

Nhớ lại rằng $1 được đặt là dữ liệu bên trong việc đối sánh đúng mẫu trong dấu ngoặc.

Hậu tố i (hoặc trước hoặc sau g nếu có) làm cho biểu thức chính qui trong toán tử thay thế bỏ qua chữ hoa thường, giống như cùng tuỳ chọn trên toán tử đối sánh đã mô tả trước đây.

Cũng vậy, giống như toán tử đối sánh, một dấu định biên khác cũng có thể được tuyển lựa nếu sổ chéo là không tiện. Chỉ cần dùng cùng kí tự đó ba lần:

s#jerry#tom#; # thay jerry bằng tom, giống s/jerry/tom/

Cũng vậy, giống toán tử đối sánh, bạn có thể xác định một mục tiêu thay phiên bằng toán tử =~. Trong trường hợp này, mục tiêu được chọn phải là một cái gì đó mà bạn có thể gán cho một giá trị vô hướng vào, như một biến vô hướng hay một phần tử của mảng. Sau đây là một thí dụ:

$which = “this is a test”;

$which =~ s/test/quiz/; # $which bây giờ là “this is a quiz”

$someplace[$here] =~ s/left/right/; # đổi một phần tử của mảng

$d{“t”} =~ s/^ /x /;

7.6 Các toán tử split() và join()

Biểu thức chính qui có thể được dùng để chặt một xâu thành các trường. Toán tử split() thực hiện điều này còn toán tử join() lại có thể dính các mẩu lại với nhau.

7.6.1 Toán tử split()

Toán tử split() nhận một biểu thức chính qui và một xâu rồi tìm tất cả mọi sự xuất hiện của biểu thức chính qui bên trong xâu này (dường như bạn đã thực hiện toán tử s///g). Các bộ phận của xâu không sánh với biểu thức chính qui sẽ được trả về lần lượt như một danh sách các giá trị. Chẳng hạn, sau đây là một cách phân tích các thành tố trong file /etc/passwd:

$line = “merlyn::118:10:Jenny:/home/merlyn:/usr/bin/perl”;

@fields = split(/:/,$line); # chặt $line ra, dùng : làm dấu định biên

# bây giờ @field là (“merlyn”, “”, “118”, “10”, “Jenny”, “/home/merlyn”, “/usr/bin/perl”)

Lưu ý rằng trường thứ hai rỗng trở thành một xâu rỗng. Nếu bạn không muốn điều này, hãy đối sánh tất cả các hai chấm trong một lần phân tách:

@fields = split(/:+/, $line);

Điều này sẽ sánh cả hai dấu hai chấm đi kèm, cho nên sẽ không có trường thứ hai rỗng nữa.

Một xâu thông dụng để chặt biến $_, và biến thành mặc định là:

$_ = “any string”;

@words = split(/ /); # hệt như @words = split(/ /, $_);

Lưu ý rằng đối với việc chặt này, các khoảng cách liên tiếp trong xâu cần chặt sẽ gây ra các trường không (xâu rỗng) trong kết quả. Một khuôn mẫu tốt hơn sẽ là / +/, hay một cách lí tưởng /\s+/, mà sẽ đối sánh một hay nhiều kí tự khoảng trắng. Trong thực tế, khuôn mẫu này là khuôn mẫu mặc định, cho nên nếu bạn định chặt biến $_ theo các khoảng trắng, thì bạn có thể dùng tất cả các mặc định và đơn thuần nói:

@words = split; # hệt như @words = split(/\s+/, $_);

Các trường theo sau rỗng không trở thành một phần của danh sách. Điều này nói chung không cần quan tâm-một giải pháp giống thế này:

$line = “merlyn::118:10:Jenny:/home/merlyn:/usr/bin/perl”;

($name,$password, $uid,$gid,$gcos,$home,$shell) = split(/:/, $line);

# chặt $line ra bằng cách dùng : làm dấu định biên

Sẽ đơn thuần cho $shell một giá trị rỗng (undef) nếu dòng này không đủ dài, hay nếu nó chứa các giá trị rỗng trong trường cuối (các trường phụ thì im lặng bị bỏ qua, vì việc gán danh sách làm việc theo cách đó).

7.6.2 Toán tử join()

Toán tử join() nhận một danh sách các giá trị và gắn chúng lại với nhau dùng xâu gắn giữa từng phần tử danh sách. Nó trông tựa như thế này:

$bigstring = join($glue, @list);

Chẳng hạn, để xây dựng lại dòng mật hiệu, bạn hãy thử một cách kiểu như:

$outline = join(“:”, @fields);

Lưu ý rằng xâu gắn không phải là biểu thức chính qui – chỉ là một xâu bình thường gồm không hay nhiều kí tự.

7.7 Bài tập

Bạn hãy viết 1 biểu thức chính qui mà nó sánh:

ít nhất 1 ký tự a, theo sau là một số lượng bất kỳ các ký tự b

một số lượng bất kỳ các dấu sổ chéo ngược (\) theo sau là một số lượng bất kỳ các dấu hoa thị (*)

ba lần xuất hiện liên tiếp của $whatever.

bất kỳ 5 ký tự nào, kể cả ký tự xuống dòng

một từ được lặp lại 2 lần (hoặc nhiều hơn) trên cùng 1 dòng.

Bạn hãy viết 1 chương trình đọc vào danh sách các từ ở <STDIN> , và tìm xem thử có dòng nào chứa cả 5 nguyên âm a,e,i,o,u hay không. Nếu có hãy in dòng đó ra màn hình. Bạn hãy chạy thử chương trình với tệp số liệu /usr/dict/words

perl myprog.pl < /usr/dict/words

(tệp /usr/dict/words có trên các máy linux và unix, nếu máy bạn không có tệp này, bạn có thể tự tạo cho mình 1 tệp số liệu mẫu để chạy thử chương trình)

(Sưu tầm từ diễn đàn tin học)


Thiết kế bởi Phạm Nguyễn Bảo Nguyên
GC Com 2005

  1. Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: