Regular Expressions: RegEx không hề khó như những gì bạn thấy (I)
Bài đăng này đã không được cập nhật trong 6 năm
Bạn có phải là một trong những người tránh xa những biểu thức chính quy (regular expressions - RegEx) vì nó trông giống như một ngoại ngữ không? Tôi cũng là một trong số đó!!!
Hãy suy nghĩ về tất cả những âm thanh, ký hiệu giao thông và mùi vị mà bạn có thể nhận ra, RegEx cũng không khác gì những thứ trên. Nó giống như một ngôn ngữ ký hiệu dùng để phân tích string
.
Trong bài viết này, chúng ta sẽ tập trung vào những biểu thức chính quy đó, ít nhất là cách nó thường xuyên được sử dụng.
Giống như bất kỳ ngôn ngữ lập trình nào, một biểu thức chính quy là một ngôn ngữ ngắn gọn theo chính cách của nó ^^.
0. Chuẩn bị môi trường
Trên thực tế, có một trang web có đầy đủ những gì bạn cần về RegEx: Regular Expressions from MDN, bạn có thể tham khảo trang web này và ngừng đọc bài viết này tại đây ^^. Nếu bạn vẫn tiếp tục, chúng ta cần một sandbox để thực hành các biểu thức này. Thật may mắn, chúng ra có thể sử dụng DevTools ngay trong console của trình duyệt của bạn.
Làm quen với cú pháp
Để bắt đầu, chúng ta sẽ sử dụng cú pháp /expression/.test('string')
.
Trong đó, expression
là một biểu thức chính quy bất kỳ mà chúng ta tạo, string
là string chúng ta muốn test. Phương thức test
trả về true
hoặc false
tùy thuộc vào string có khớp với expression hay không. Dấu gạch chéo /
đánh dấu điểm bắt đầu và kết thúc của expression, hãy hiểu nó giống như khi bạn dùng "
hoặc '
để đánh dấu điểm bắt đầu và kết thúc của một string.
Biểu thức ở giữa /
là một chữ hoặc các ký tự chữ.
Bây giờ, hãy mở console của trình duyệt và thử chạy:
/a/.test("a"); //true
/a/.test("b"); //false
Nếu code trên hoạt động, chúng ta đã sẵn sàng, đừng lo lắng chúng là gì, chúng ta sẽ tìm hiểu ngay bên dưới.
1. Bắt đầu nhỏ với các chữ cái
Hãy bắt đầu với các ví dụ nhỏ. Chúng ta cần phải tìm ra liệu một string có chứa ký tự được mô tả hay không.
Ví dụ: tìm ký tự a
trong một string
/a/.test("abc"); //true
/a/.test("bcd"); //false
/a/.test("cba"); //true
Biểu thức đã thực hiện những gì chúng ta muốn: Tìm ký tự a
trong một string. Trong ví dụ, abc
và cba
có chứa ký tự a
nhưng bcd
thì không.
Thực tế, chúng ta có thể gán /a/
vào một biến để dễ nhìn hơn:
let e = /a/;
e.test("abc"); //true
e.test("bcd"); //false
e.test("cba"); //true
Trong ví dụ trên, biểu thức giữa 2 dấu gạch chéo /
chỉ là một ký tự đơn, chúng ta đang chỉ tìm kiếm cho một ký tự.
Tìm kiếm nhiều ký tự Thế nếu bạn muốn tìm nhiều hơn một ký tự thì sao? Hãy đặt chúng theo đúng thứ tự và coi chúng như một chuỗi con (substring) của string. Đây là ví dụ:
/ab/.test("abacus"); //true
/bac/.test("abacus"); //true
/abc/.test("abacus"); //false
/abas/.test("abacus"); //false
String được kiểm tra phải chứa chính xác cụm từ trong các dấu gạch chéo. Chúng ta có được một kết quả đúng nếu điều kiện đó được đáp ứng.
bac
nằm trong abacus
nhưng abas
không có trong abacus
. Mặc dù chúng ta có đầy đủ các ký tự đó, nhưng chúng ta không nhận được kết quả chính xác do các ký tự không đúng thứ tự trong string.
Tóm tắt lại
Ký hiệu /.../
. Dấu gạch chéo (/
) đánh dấu điểm bắt đầu và kết thúc của một biểu thức chính quy. Bỏ qua các dấu chấm, đó là nơi chúng ta đặt các pattern. Ký tự /a/
giữa các dấu gạch chéo là một pattern khớp với string được test. Các ký tự /abc/
giữa các dấu gạch chéo được tìm kiếm như một chuỗi con trong suốt quá trình kiểm tra với string được test.
2. Patterns với các chữ số
Hãy thêm gia vị lên một chút. Hãy nói rằng bạn muốn tìm hiểu xem liệu một string có đầy đủ các ký tự số hay không.
let e = /0|1|2|3|4|5|6|7|8|9/;
e.test("42"); //true
e.test("The answer is 42"); //true
Trước hết, pattern trông khá dài. Nhưng dải dài các ký tự đó có thể được thể hiện chỉ trong hai ký tự. Chúng ta sẽ nói đến nó sau ở cuối phần này.
Trường hợp thứ 2 nhẽ ra không phải là true
. Chúng ta sẽ giải quyết nó một lát sau.
Bây giờ, dấu sổ thẳng (|
) ở đây nghĩa là OR
. Bên ngoài của biểu thức chính quy, chúng được sử dụng như một toán tử thao tác trên bit (bitwise) OR
và điều kiện OR
với dấu sổ thẳng kép (||
).
Vậy chúng ta có nên viết 9 dấu sổ thẳng không? Không! Giờ hãy viết lại:
e=/[0123456789]/;
e.test("42"); //true
e.test("The answer is 42"); //still true
Giờ chúng trông tốt hơn, 9 dấu sổ thẳng giờ được thay thế bởi 2 dấu ngoặc vuông, ta tiết kiệm được 7 ký tự! Bằng cách này, giá trị trong dấu ngoặc vuông sẽ được hiểu là: hoặc là cái này hoặc là cái kia. Đó là một tập hợp các ký tự. Trong trường hợp của chúng ta, string nên bao gồm hoặc là 0, hoặc là 1, hoặc là 2 hoặc là 3 hoặc là ...
Sao? Bạn đang nói gì? Trông nó vẫn dài? Bạn không hài lòng? Ok, giờ ta hãy thử lại:
e=/[0-9]/;
e.test(42); //true
e.test("42"); //true
e.test("The answer is 42"); //true!
Thế này thì sao? Trông nó gọn gàng hơn đúng không? Mọi thứ trong dấu ngoặc vuông nghĩa là OR
. 0-9
đánh dấu một range
, nghĩa là từ 0 tới 9. Vì vậy, ví dụ thực hiện yêu cầu tìm kiếm các ký tự từ 0 tới 9 trong chuỗi cần test.
The prefix and suffix patterns - pattern với tiền tố và hậu tố
Giờ chúng ta sẽ giải quyết trường hợp không thành công thứ 2. "The answer is 42"
đã khớp với test của chúng ta bởi vì pattern của chúng ta tìm kiếm các ký tự số ở bất kỳ đâu trong string, không phải là ở đầu hay cuối.
Và giờ ^
và $
sẽ giúp chúng ta.
^
nghĩa là bắt đầu của string.$
nghĩa là kết thúc của string.
Giờ là ví dụ với tiền tố pattern:
/^a/.test("abc"); //true
/^a/.test("bca"); //false
/^http/.test("https://pineboat.in"); //true
/^http/.test("ftp://pineboat.in"); //false
Bất kỳ pattern nào theo sau ^
nên là bắt đầu của string đang test.
String thứ 2 bắt đầu với b
trong khi pattern của chúng ta đang tìm kiếm a
. String thứ 4 tìm kiếm http
trong khi nó bắt đầu với ftp
. Đó là lý do kết quả là false
.
The suffix patterns
$
ở cuối của pattern nghĩa là tìm kiếm pattern ở cuối của string.
/js$/.test("regex.js"); //true
/js$/.test("regex.sj"); //false
Điều này có thể khiến bạn nghĩ rằng: "Tìm kiếm js
và sau đó kết thúc string", nhưng tốt hơn nên hiểu là "Hãy tìm một string có kết thúc là js
".
Pattern match End to End
let e=/^[0-9]$/
e.test("42"); //false - NO!
e.test("The answer is 42"); //false
e.test("7"); //true
Thật ngạc nhiên, ví dụ đầu tiền thất bại khi chúng ta thêm ^
và $
.
/^[0-9]$/
được hiểu là: "Đi từ đầu string, tìm kiếm một ký tự số đơn và kiểm tra xem string có kết thúc tại đó không". Đó là lý do tại sao ví dụ cuối trả về true
. String chỉ là một số đơn, bắt đầu và cũng là kết thúc.
Nhưng đó không phải là điều chúng ta muốn, chúng ta muốn test xem liệu string có một hoặc nhiều hơn một chữ số hay không.
Câu chuyện về ba chàng lính ngự lâm
Một dấu chấm hỏi (?
), một dấu cộng (+
) và một dấu hoa thị (*
) gặp nhau tại trận đấu, mỗi người trong đó có những khả năng khác biệt.
Dấu hỏi chấm khiêm tốn nói: "Tôi có thể nhìn thấy không có thứ gì hoặc chỉ nhìn thấy một thứ".
Dấu cộng (+
) nói: " Tôi cần thấy ít nhất một thứ hoặc nhiều hơn".
Dấu hoa thị (*
) nói: "Tôi có thể làm được cả hai, tôi có thể nhìn thấy không có thứ gì, chỉ một thứ hoặc nhiều hơn".
Một trong số chúng đang khéo léo che giấu những gì chúng có thể làm.
Dấu hỏi chấm biểu diễn đầu tiên:
/a?/.test(""); //true
/a?/.test("a"); //true
/a?/.test("b"); //true!
/a?/.test("aa"); //true
/^a?$/.test("aa"); //false
- Tìm trong string rỗng
""
:?
viết tắt cho 0 hoặc 1 - Tìm trong
a
: một kết quả khớp - Tìm trong
b
: không có cái nào khớp - Tìm trong
aa
:a
đầu tiên khớp với pattern vàa
thứ 2 không phải là một phần của pattern. /^a?$/
không khớp vớiaa
: Nó tìm kiếm 0 hoặc 1 ký tựa
, đầu và cũng là cuối, không nhiều hơn, không ít hơn.
Dấu cộng (+
) nhìn vào dấu hỏi chấm và nhận xét: "Tôi đã rất ấn tượng, nhưng trọng tâm của bạn giống như hệ nhị phân vậy". Và nó bước lên bục biểu diễn:
/a+/.test("a"); //true
/a+/.test("aa"); //true
/a+/.test("ba"); //true!
/^a+$/.test("aa"); //true
/a+/.test(""); //false
/a+/.test("b"); //false
/^a+$/.test("ab"); //false
Bạn có nhớ những gì dấu cộng đã nói không? Nó có thể bắt một hoặc nhiều lần xuất hiện của pattern đứng trước nó.
Tất cả các trường hợp trả về true
đều có một hoặc nhiều ký tự a
. Chúng ta thậm chí tìm cách để có được một chuỗi toàn bộ chỉ bao gồm a
trong trường hợp cuối cái mà trả lại true
với / ^ a + $ /
.
Các trường hợp fasle
bây giờ đã được làm rõ, nhưng một từ ở cuối lại trả về false
. /^a+$/
tìm kiếm a
ở đầu và cũng là ở cuối cùng, không có ký tự nào khác được cho phép ở đây, đó là lý do tại sao ab
khi test bị fail.
Cuối cùng, dấu sao (*
) biểu diễn. Cậu ta tự hào rằng: "Tôi có thể đấu một mình hoặc đấu với cả 2 bạn cùng lúc" và nói "Tôi có thể match không cái nào, một cái hoặc nhiều cái".
/a*/.test("a"); //true
/a*/.test("aa"); //true
/a*/.test("ba"); //true
/a*/.test(""); //true
/a*/.test("b"); //true
/^a*$/.test("aa"); //true
/^a*$/.test(""); //true
/^a*$/.test("ab"); //false
Ngoại trừ trường hợp cuối cùng, *
có thể xử lý tất cả những cái khác. /^a*$/
có thể hiểu là: 0 hoặc nhiều hơn ký tự a
là đầu cũng là cuối. Đó là tại sao string rỗng ""
pass test trong khi ab
fail.
Quay lại câu trả lời lúc trước
Bạn có nhớ chúng ta đã từng gặp qua trường hợp ba chàng lính ngự lâm ở đâu trước đó không? Đúng rồi, đó là trường hợp “The answer is 42”
.
Bây giờ, nếu ta cần tìm kiếm chỉ các chữ số, một hoặc nhiều hơn các ký tự, chúng ta sẽ làm gì?
//Let's throw in a plus
let e=/^[0-9]+$/
e.test("4"); //true
e.test("42"); //true
e.test("The answer 42"); //false - Hurray
Dấu cộng (+
) trong [0-9]+
đã đưa cho chúng ta một giải pháp. Cộng nghĩa là nhiều hơn một lần xuất hiện của ký tự hoặc pattern ở đằng trước nó. Trong trường hợp của chúng ta, nhiều hơn một chữ số.
Nó vẫn trả về false
trong tường hợp The answer 42
bởi vì không có chữ số nào bắt đầu ở đầu string.
Oh, mình quên mất, [0-9]
viết tắt cho bất kỳ một tập chữ số nào và có cách viết ngắn hơn \d
.
let e=/^\d+$/;
e.test("4"); //true
e.test("42"); //true
e.test("The answer 42"); //false - Hurray
Chỉ cần 2 ký tự để biểu thị cho các chữ số và không có cách nào viết ngắn hơn nữa!
Có một vài pattern đặc biệt đặc tả cho các tập khác nhau: tập các số (\d
), tập các ký tự apha (\w
), dấu cách (\s
).
Review
[123]
Biểu thức trong dấu ngoặc vuông là một tập các ký tự, bất kỳ ký tự nào khớp với string sẽ pas test. Chỉ một ký tự thôi.[0-9]
Tìm kiếm một chữ số đơn từ 0 đến 9.[0-5]
Tìm kiếm một chữ số đơn từ 0 đến 5.[a-z]
Tìm kiếm một chữ cái đơn từ a đến z.[A-F]
Tìm kiếm một chữ cái đơn từ A đến Z.[123]+
dấu+
tìm kiếm một hoặc nhiều hơn số lần xuất hiện của ký tự trong tập.|
Dấu sổ thẳng viết tắt choOR
.\d
Viết tắt cho tập các chữ số, chỉ match với một chữ số duy nhất.\D
Viết tắt cho tập các ký tự không phải là số, là tất cả các ký tự khác với các số được match với\d
.
Trên đây là một số hiểu biết cơ bản về biểu thức chính quy, chúng ta sẽ tiếp tục tìm hiểu nhiều hơn về nó trong bài viết sau. Cảm ơn bạn đã đọc bài viết!
All rights reserved