Regex và những ứng dụng hay ho

1. Regex là gì ?

Nếu như đã lập trình một thời gian thì chắc hẳn regex (viết tắt của Regular Expression, tên thuần Việt là biểu thức chính quy) không phải là một điều xa lạ với các bạn nữa. Khi kiểm tra tính hợp lệ của email hoặc số điện thoại thì điều bạn nghĩ tới đầu tiên chính là regex rồi. Nhưng không chỉ như vậy, regex còn nhiều ứng dụng khác, cùng tìm hiểu dần nhé !

Hiểu đơn giản thì regex là một chuỗi các kí tự miêu tả một bộ các chuỗi ki tự khác, theo những quy tắc và cú pháp nhất định.

2. Review về regex.

Chắc chắn rằng nếu bạn muốn theo con đường trở thành một developer thực thụ thì regex là thứ không thể thiếu trong hành trang của bạn, lại càng hữu ích với các developer hay làm việc với linux, dù có thể không biết nhiều nhưng cũng cần chắc ke những cú pháp cơ bản.

2.1 Ăn xổi

Khi mới dùng regex, mọi người thường tham khảo những syntax đơn giản nhất, ở mức cơ bản.

Sau đó khi làm tới những requirement phức tạp hơn thì lên google search, chắc hẳn các bạn đã từng nhìn thấy những bài viết với tiêu đề kiểu như: 30 đoạn mã regex cần biết cho lập trình viên, 40 đoạn mã regex thông dụng nhất, 50 abc xyz, vân vân và mây mây ...

Có một vài ý kiến về những đoạn regex "ăn xổi" như không hiểu bản chất, không sát yêu cầu, độ tin cậy không cao ... Đôi khi có những bài viết phản đối khá mạnh điều này.

Thực ra học regex ở mức cơ bản khá là đơn giản, với những requirement tầm thấp và trung, chịu khó đọc một chút thì có thể tự viết regex cho mình. Dùng cái gì, code ra dòng nào mà mình hiểu tường tận nó từ đầu tới cuối bao giờ cũng phê hơn nửa chừng nửa vời.

Nhưng còn với requrment tầm cao thì khác, regex không phải là thứ gì đó dễ học và dể hiểu, nó cũng khó debug, nếu đi sâu sẽ có rất nhiều khái niệm phức tạp character classes, alternation mà không phải ai cũng hiểu hết và với nhu cầu cơ bản thì thiết nghĩ hiện tại mình cũng chưa cần thiết tìm hiểu sâu tới như vậy.

Chưa kể như những đoạn regex ăn xổi sẽ gợi ý giúp bạn những idea để bạn tự tham khảo và viết lại theo cách của mình, cả những khi sắp tới deadline của dự án nữa...

2.2 Khó khăn

Không phải lúc nào cũng nên dùng regex, thay vì mò mẫm với chuỗi regex dài cả cây số như cực hình bạn hãy thử cân nhắc những phương án khác nếu có. Học regex mình thấy có 2 vấn đề:

  • Ngại đọc:

    Khi nhìn vào đoạn mã regex sau và thử giải thích nó: /^\|\s*(\d*)?\s*\|\s*(.+)?\s*\|\s*\d*\s*\|\s*\d*-(\d*)?/

    Vâng ngại thật, rất may là có khá nhiều trang web online giúp bạn test được chuỗi regex của mình. Rubular là một ví dụ.

  • Dễ quên:

    Đã bao giờ bạn đã nhớ rất rõ những cú pháp, nhưng thời gian sau đã quên béng đi và tìm kiếm lại, chưa kể tới việc lẫn lộn với nhau.

    Vâng, đó là một trong những lý do mình viết bài này, phần 4 như một kho lưu trữ, khi quên mình sẽ quay trở về tìm kiếm nó và cập nhật liên tục.

    Một chút nữa là mỗi ngôn ngữ sẽ có cú pháp áp dụng regex khác nhau, nhưng không sao, search google chút là ok.

Ngày đi thực tập, anh hướng dẫn mình đã khuyên mình nên tạo một repo documentation trên github, lưu trữ lại tất cả câu lệnh dài, khó nhớ hoặc hay sử dụng (câu lệnh trong docker hay câu lệnh với composer,... câu lệnh trong linux hay git... )

Khi cần tra cứu lại rất nhanh, tiết kiệm được kha khá thời gian, và đến giờ mình luôn làm theo điều này.

3. Ứng dụng của regex

Ở đây, ngôn ngữ ruby được sử dụng để làm ví dụ nhé ! Vai trò của ngôn ngữ ở đây không quan trọng lắm.

3.1 So khớp

Cái này là điển hình nhất rồi. Ví dụ: Nếu muốn tìm các từ có kí tự a đứng đầu, b đứng cuối và chứa kí tự số. Khi đó ta sử dụng chuỗi regex như sau: ^a.*[0-9].*b$

Code:

/^a.*[0-9].*b$/.match?("a7eb") => true

/^a.*[0-9].*b$/.match?("a7ebs") => false

3.2 Tách chuỗi

Giả sử bạn có tập dữ liệu như sau:

Yêu cầu cần lấy ra id, name, month of update_at của mỗi bản ghi. Ghi kết quả như sau:

1|C|07 2|C#|07 3|C++|07

Lúc này regex khá hữu dụng đấy:

text = File.open("data.txt").read
regex = /^\|\s*(\d*)?\s*\|\s*(.+)?\s*\|\s*\d*\s*\|\s*\d*-(\d*)?/
File.write("result.txt", "")
text.each_line do |line|
  match_words = line.match(regex).to_a
  File.write("result.txt", match_words[1] + "|" + match_words[2].gsub(/\s+$/,"") + "|" + match_words[3] + "\n", mode: "a") unless match_words.empty?
end

Quan trọng nhất là chuỗi regex kia đúng. Ngại thì ngại nhưng cứ lần lượt đọc từ trái qua phải thì cũng dễ hiểu thôi.

Note: (\d)? có nghĩa là regex sẽ trả về kết quả trong cặp ngoặc nhọn, ở đây nó sẽ tương ứng với match_words[1] và giá trị trả về là 1,2,3,...

3.3 Tìm kiếm

Phần lớn các IDE hiện nay đều tích hợp chức năng này. Mình sẽ demo với Sublime Text.

  • Ấn tổ hợp phím Ctrl + H

  • Kích hoạt chế độ Regular Expesion bẳng cách kích chuột vào biểu tượng .* (Vị trí đầu tiên cùng hàng với ô Find) hoặc tổ hợp phím Alt + R.

  • Nhập biểu thức chính quy và ô Find.

  • Bạn sẽ thấy ngay kết quả được khoanh tròn.

3.4 Thay thế

Tiếp tục nhập nội dung bạn cần thay thế vào ô Replace. Click vào Replace All và kết quả

Một ví dụ khác cho thấy sức mạnh của regex

====>

3.5 Regex với grep

Bức ảnh này đã nói lên tất cả rồi đúng không ?

4. Cách viết regex.

Bảng phong thần đây rồi ...

4.1 Kí tự thường

STT Biểu thức Mô tả Ghi chú
01 a|b Khớp với a hoặc b
02 [0-9] Khớp với một trong số từ 0 tới 9
03 [a-z] Khớp với một trong chữ từ a tới z
04 [abc] Có thể khớp với a, b hoặc c
05 [^abc] Khớp với bất kì kí tự nào ngoài a, b và c Nếu ^ xuất hiện đầu tiên sau ngoặc vuông, nó có nghĩa là phủ định
STT Biểu thức Mô tả Ghi chú
06 \d Số bất kì Thay thế cho [0-9]
07 \D Ký tự không phải là số Thay thế cho [^0-9]
08 \s Kí tự khoảng trắng
09 \S Không phải kí tự khoẳng trắng Thay thế cho [^\s]
10 \S+ Một số kí tự không phải khoẳng trắng Một hoặc một số
11 \w Kí tự chữ Thay thế cho [a-zA-Z0-9]
12 \W Kí tự không phải chữ Thay thế cho [^\w]
13 \b Ký tự thuộc a-z hoặc A-Z hoặc 0-9 hoặc _ Thay thế cho [a-zA-Z0-9_]

4.2 Kí tự đặc biệt

STT Biểu thức Mô tả Ghi chú
14 . Khớp với bất kỳ ký tự đơn nào ngoại trừ \
15 ^ Bắt đầu của từ
16 $ Kết thúc của từ
17 / Bắt đầu hoặc kết thúc chuỗi regex
18 Sủ dụng tương đương phép or Hay dùng trong cặp ngặc tròn
19 \ Biểu diễn một kí tự ngay sau nó từ kí tự đặc biệt thành kí tự thường và ngược lại VD: \b sẽ trở thành như mình nói ở trên, * sẽ trở thành kí tự * chứ không phải số lần lặp nữa

4.3 Lặp

STT Biểu thức Mô tả Ghi chú
20 * Xuất hiện 0 hoặc nhiều lần viết ngắn gọn cho {0,}
21 + Xuất hiện 1 hoặc nhiều lần viết ngắn gọn cho {1,}
22 ? Xuất hiện 0 hoặc 1 lần viết ngắn gọn cho {0,1}
23 {X} Xuất hiện X lần X không phải số âm
24 {X,Y} Xuất hiện trong khoảng X tới Y lần X,Y không phải số âm
25 ? có nghĩa là xuất hiện 0 hoặc nhiều lần, thêm ? phía sau nghĩa là tìm kiếm khớp nhỏ nhất

4.4 Khớp nhóm

STT Biểu thức Mô tả Ghi chú
26 () Khớp với một nhóm các kí tự đồng thời nhớ kết quả khớp Ví dụ (e|g)mail sẽ khớp với email hoặc gmail. /(ab) (cd) \1 \2/ sẽ khớp với "ab cd ab cd"
27 (?:x) Khớp với x nhưng không nhớ kết quả khớp "foo foo" sẽ khớp với /(foo) \1/ chứ không khớp với (?:foo) \1
28 x(?=y) Chỉ khớp x nếu ngay sau x là y "hello" sẽ khớp với /h(?=e)/ nhưng kết quả trả về chỉ có h
29 x(?!y) Chỉ khớp x nếu ngay sau x không phải là y

Tham khảo thêm tại đây