Bài 03: Các quy tắc Regular Expression căn bản (tiếp theo)

Tiếp tục serie học Regular Expression trong PHP thì trong bài này ta sẽ tiếp nối bài trước đó là tìm hiểu các quy tắc căn bản trong Regular Expression, bài này mình sẽ bổ sung một số quy tắc căn bản chứ chưa đi vào phần nâng cao. 

1. Regex đại diện cho một ký tự

Bài trước ta đã được học cách xác định phạm vi của một chuỗi bằng cách dùng cú pháp [min-max] hoặc [list_char], nhưng giả sử tôi muốn một Regex chấp nhận một ký tự bất kì nào đó thì không thể sử dụng cú pháp đó được. Và thật may trong Regular Expression đã cho ta một cách đó là dùng ký tự . để định nghĩa cho một kí tự bất kì.

Ví dụ:

// Pattern là ký tự bất kỳ dài từ 3 đến 10 ký tự
$pattern = '/^.{3,10}$/';
$subject = '3232';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}

2. Ký hiệu đặc biệt cho các từ khóa Regex

Giả sử giờ tôi cần kiểm tra một chuỗi có tồn tại dấu chấm . hay không? Nếu áp dụng kí tự . thì ta sẽ làm như sau:

// Kết quả ko mong đợi
$pattern = '/./';
$subject = 'demo';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}

Chạy đoạn code này lên kết quả trả về true, điều này sai với kết quả ta mong đợi vì trong $subject không tồn tại dấu chấm .. Lý do Regular Expression đã hiểu nhầm ký tự dấu chấm ở $pattern là đại diện cho ký tự bất kì nên nó so khớp với $subject là hoàn toàn đúng.

Những ký tự như dấu chấm ., mở ngoặc và đóng ngoặc vuông [], hoặc những ký tự liên quan đến quy tắc của Regular Expression đều được quy về dạng ký tự đặc biệt trong Regular Expression. Vì thế để phân biệt giữa ký tự đặc biệt Regex và ký tự bình thường thì ta thêm dấu \ vào đầu ký tự đó.

Như ở ví dụ trên tôi sẽ code lại như sau:

// Dấu chấm là ký tự đặc biệt trong regex nên phải thêm dấu \
$partern = '/\./';
$subject = 'demo';
if (preg_match($partern, $subject)){
    echo 'Chuỗi regex so khớp';
}

Chạy lên kết quả như mong đợi đó là trả về FALSE vì $subject = 'demo', nếu bạn đổi giá trị $subject = '.' thì kết quả sẽ TRUE.

3. Regex A hoặc B

Giả sử tôi cần kiểm tra $subject = 'A' hoặc bằng $subject = 'B' sẽ trả về đúng thì ta dùng dấu |, đây là kí hiệu biểu diễn mối quan hệ OR.

$pattern = '/^A|B$/';
$subject = 'A';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}

Kết quả sẽ trả về TRUE. Bây giờ bạn thử thay đổi giá trị của $subject và kiểm tra lại nhé.

4. Gom nhóm Regex lại

Đôi lúc ta muốn gom nhóm Regex lại cho dễ nhìn, việc này đơn giản ta chỉ cần đặt đoạn mã Regex bên trong cặp đóng và mở (). Khi sử dụng gom nhóm thì việc so khớp vẫn bình thường, tuy nhiên với kết quả về của biến $matches thì sẽ có sự thay đổi và chi tiết thế nào thì ở phần Capturing Group dưới đây mình sẽ đề cập tới.

Ví dụ:

// Gom nhóm A hoặc B lại thành 1 nhóm
$pattern = '/(A|B)/';
$subject = 'A';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}

5. Regex kiểm tra chiều dài không giới hạn

Ở bài trước ta đã được học để xác định chiều dài cho chuỗi Regex $pattern thì dùng cú pháp {min,max}, tuy nhiên vẫn còn một số quy tắc ngắn gọn hơn đó là sử dụng các ký tự *, +, ? để thiết lập chiều dài cho chuỗi.

Ký tự *

Đại diện cho không hoặc nhiều ký tự.

Ví dụ: kiểm tra chuỗi có phải trống hoặc là gồm các chữ cái thường.

// Chuỗi có phải trống hoặc có những chữ cái in thường 
$pattern = '/[a-z]*/';
$subject = 'dsada';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}

Với bài này ta cũng có thể giải cách sau:

// Chuỗi có phải trống hoặc có những chữ cái in thường 
$pattern = '/[a-z]{0,}/';
$subject = 's';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}

Ký tự +

Đại diện cho 1 hoặc nhiều ký tự.

Ví dụ: kiểm tra chuỗi có ít nhất một chữ thường

// chuỗi ít nhất có 1 ký tự chữ thường
$pattern = '/[a-z]+/';
$subject = 's';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}

Với bài này ta có thể giải bằng cách sau:

// chuỗi ít nhất có 1 ký tự chữ thường
$pattern = '/[a-z]{1,}/';
$subject = 's';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}

Ký tự ?

Đại diện cho một hoặc không có ký tự nào

Ví dụ:

// chuỗi có 1 hoặc không có ký tự thường nào
$pattern = '/[a-z]?/';
$subject = 's';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}

Bài này ta có thể giải bằng cách sau:

// chuỗi có 1 hoặc không có ký tự thường nào
$pattern = '/[a-z]{0,1}/';
$subject = 's';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}

6. Regex phủ định - NOT

Ta dùng ký tự ^ để phủ định một Regex nào đó, ví dụ trả về đúng nếu $subject không phải là số.

// Chuoi không có ký tự số
$pattern = '/[^0-9]{1,2}/';
$subject = 'sd';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}

Các ký tự Regex đặc biệt

Danh sách các ký tự Regex đặc biệt như sau:

  • \d - Chữ số bất kỳ ~ [0-9]
  • \D - Ký tự bất kỳ không phải là chữ số (ngược với \d) ~ [^0-9]
  • \w - Ký tự từ a-z, A-Z, hoặc 0-9 ~ [a-zA-Z0-9]
  • \W - Ngược lại với \w (nghĩa là các ký tự không thuộc các khoảng: a-z, A-Z, hoặc 0-9) ~[^a-zA-Z0-9]
  • \s - Khoảng trắng (space)
  • \S - Ký tự bất kỳ không phải là khoảng trắng.

Ví dụ: kiểm tra một chuỗi là số hoặc không phải là số

// Kiểm tra là số
$pattern = '/\d/';
$subject = '2';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}


// Kiểm tra không phải là số
$pattern = '/\D/';
$subject = 'dsd';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}

7. Capturing value trong Regular Expression

Capturing value là gì? để trả lời câu hỏi này tôi sẽ đưa ra một đoạn RegEx để mổ xẻ nó nhé  $pattern = '/([a-z]+)([0-9]+)/'.

Trong đoạn $pattern trên ta sẽ phân tích thành 3 phần RegEx khác nhau:

  • Phần 1: Là toàn bộ pattern, tức là ([a-z]+)([0-9]+)
  • Phần 2: Là đoạn con nhỏ pattern đầu tiên ([a-z]+)
  • Phần 3: Là đoạn con nhỏ cuối cùng ([0-9]+)

Những phần trên ta gọi là Captering Value Trong Regular Expression.

Ví dụ

preg_match('/([a-z]+)([0-9]+)/', 'freetuts2014', $matches);
echo '<pre>';
print_r($matches);
echo '</pre>';

Kết quả:

Array
(
    [0] => freetuts2014
    [1] => freetuts
    [2] => 2014
)

Các bạn thấy:

  • Phần tử thứ nhất tương ứng với Phần 1.
  • Phần tử thứ hai tương ứng với Phần 2
  • Phần tử thứ ba tương ứng với Phần 3

Các bạn phải nắm định nghĩa này nhé, nó rất là quan trong khi các bạn kết hớp xử lý với các hàm như preg_replace, preg_match trong php

8. Greedy trong Regular Expression

Greedy là gì? Cũng như phần Capturing Value tôi sẽ đưa ra một ví dụ cho dễ hiểu nhé.

// Tìm chuỗi bắt đầu bằng h và kết thúc chữ o
preg_match('/h(.+)o/', 'hello la xin chao', $matches);
echo '<pre>';
print_r($matches);
echo '</pre>';

Chạy và kết quả như sau:

Array
(
    [0] => hello la xin chao
    [1] => ello la xin cha
)

Tại sao kết quả tứ lung tung thế nhỉ. Tại vì nó tìm chữ h và nó thấy chữ h ngay chuỗi hello đầu tiên, tiếp theo là một chuỗi bất kì và kết thúc là chữ o nên nó duyệt và thấy chữ o ở ngay chữ chao nên nó lấy từ đầu đến cuối. Như vậy có cách nào để lấy chứ hello đầu tiên không? Bạn xem code nhé.

// Tìm chuỗi bắt đầu bằng h và kết thúc chữ o
preg_match('/h(.+?)o/', 'hello la xin chao', $matches);
echo '<pre>';
print_r($matches);
echo '</pre>';

Chạy lên kết quả sẽ là:

Array
(
    [0] => hello
    [1] => ell
)

Đây chính là kết quả ta mong đợi, sự khác biệt giữa 2 cách là ở cách 2 tôi thêm dấu ? ở kế ngay dấu + ở chuỗi pattern.

Vậy Greedy trong Regular Expression là tính chất tham ăn, nó lấy hết cho tới khi gặp ký tự cuối cùng. Và ta dùng dấu ? đặt sau các Regex để khắc phục tình trạng Greedy.

9. Các ví dụ về Regular Expression

Kiểm tra chuỗi có phải định dạng năm hay không?

// Pattern đúng nếu là chữ A hoặc chữ B
// Kiểm tra một chuỗi có phải là định dạng năm không,
// Gợi ý: Định dạng năm có 4 ký tự số
$pattern = '/^\d{4}$/';
$subject = '1234';
if (preg_match($pattern, $subject)){
    echo 'Năm đúng';
}

kiểm tra chuỗi có phải là số hay không

$pattern = '/^[0-9]+$/';
$subject = '1234';
if (preg_match($pattern, $subject)){
    echo 'Chuoi la số';
}

kiểm tra chuỗi phải là dạng ngay/thang/nam hay không

Vú dụ ngày 20/06/2015,

$pattern = '/^[0-9]{2}/[0-9]{2}/[0-9]{4}$/';
$subject = '12/10/2014';
if (preg_match($pattern, $subject)){
    echo 'Đúng định dạng ngày tháng năm';
}

Kiểm tra chỗi là ABC hoặc CDF

// Chuỗi là ABC hoặc là CDF
$pattern = '/^(ABC)|(CDF)$/';
$subject = 'ABC';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi so khớp';
}

Ví du này tôi đã dùng kỹ thuật gom nhóm Capturing Value và dùng toáng tử OR.

10. Lời kết

Như vậy là mình đã giới thiệu với các bạn đầy đủ các quy tắc căn bản thường sử dụng trong Regular Expression, vẫn còn một số kiến thức nâng cao nữa như LookAhead, LookBehind, Condition, ... nhưng mình sẽ giới thiệu mở một bài khác. Và mình nhắc luôn là phần GreedyCapturing Value rất là quan trọng nên bắt buộc bạn phải hiểu nó thì ở các bài tiếp theo mới tiếp thu bài học được.

Hãy để lại link bài viết gốc khi chia sẻ bài viết này, mình sẽ report DMCA với những website lấy nội dung mà không để nguồn hoặc copy bài với số lượng lớn.

Nguồn: freetuts.net

Profile photo of adminTheHalfHeart

TheHalfHeart

Có sở thích viết tuts nên đã từng tham gia viết ở một số diễn đàn, đến năm 2014 mới có điều kiện sáng lập ra freetuts.net. Sinh năm 90 và có 1 vợ 2 con, thích ca hát và lập trình.

ĐĂNG BÌNH LUẬN: Đăng câu hỏi trên Group Facebook để được hỗ trợ nhanh nhất.