TL; DR
Sử dụng [.]
thay vì \.
và [0-9]
thay vì \d
để tránh các sự cố thoát trong một số ngôn ngữ (như Java).
Cảm ơn người vô danh đã nhận ra điều này.
Một mẫu tương đối đơn giản để khớp số dấu phẩy động là
[+-]?([0-9]*[.])?[0-9]+
Điều này sẽ phù hợp với:
Xem một ví dụ làm việc
Nếu bạn cũng muốn đối sánh 123.
(dấu chấm không có phần thập phân), thì bạn sẽ cần một biểu thức dài hơn một chút:
[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)
Xem câu trả lời của pkeller để có lời giải thích đầy đủ hơn về mẫu này
Nếu bạn muốn bao gồm các số không phải số thập phân, chẳng hạn như hex và bát phân, hãy xem câu trả lời của tôi cho Làm cách nào để xác định xem một chuỗi có phải là số hay không? .
Nếu bạn muốn xác thực rằng đầu vào là một số (thay vì tìm một số trong đầu vào), thì bạn nên bao quanh mẫu bằng ^
và $
, như sau:
^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$
Biểu thức chính quy bất thường
"Cụm từ thông dụng", như được triển khai trong hầu hết các ngôn ngữ hiện đại, API, khuôn khổ, thư viện, v.v., dựa trên một khái niệm được phát triển trong lý thuyết ngôn ngữ chính thức . Tuy nhiên, các kỹ sư phần mềm đã thêm nhiều phần mở rộng đưa những triển khai này vượt xa định nghĩa chính thức. Vì vậy, trong khi hầu hết các công cụ biểu thức chính quy giống nhau, thực tế không có tiêu chuẩn nào. Vì lý do này, rất nhiều phụ thuộc vào ngôn ngữ, API, khuôn khổ hoặc thư viện bạn đang sử dụng.
(Ngẫu nhiên, để giúp giảm thiểu nhầm lẫn, nhiều người đã sử dụng " regex " hoặc " regexp " để mô tả các ngôn ngữ đối sánh nâng cao này. Xem Regex có giống Biểu thức chính quy không? Tại RexEgg.com để biết thêm thông tin.)
Điều đó nói rằng, hầu hết các công cụ regex (thực tế, tất cả chúng, theo như tôi biết) sẽ chấp nhận \.
. Rất có thể, đã xảy ra sự cố khi thoát.
Rắc rối với việc chạy trốn
Một số ngôn ngữ có hỗ trợ sẵn cho regex, chẳng hạn như JavaScript . Đối với những ngôn ngữ không có, thoát có thể là một vấn đề.
Điều này là do về cơ bản bạn đang viết mã bằng một ngôn ngữ trong một ngôn ngữ. Java, ví dụ, sử dụng \
như một ký tự thoát trong chuỗi của nó, vì vậy nếu bạn muốn đặt một ký tự dấu gạch chéo ngược theo nghĩa đen trong một chuỗi, bạn phải thoát nó:
// creates a single character string: "\"
String x = "\\";
Tuy nhiên, regexes cũng sử dụng \
ký tự để thoát, vì vậy nếu bạn muốn khớp một ký \
tự theo nghĩa đen , bạn phải thoát ký tự đó cho công cụ regexe và sau đó thoát lại cho Java:
// Creates a two-character string: "\\"
// When used as a regex pattern, will match a single character: "\"
String regexPattern = "\\\\";
Trong trường hợp của bạn, có thể bạn đã không thoát khỏi ký tự gạch chéo ngược trong ngôn ngữ bạn đang lập trình:
// will most likely result in an "Illegal escape character" error
String wrongPattern = "\.";
// will result in the string "\."
String correctPattern = "\\.";
Tất cả những điều này có thể rất khó hiểu. Nếu ngôn ngữ bạn đang làm việc hỗ trợ chuỗi thô , thì bạn nên sử dụng những ngôn ngữ đó để cắt giảm số lượng dấu gạch chéo ngược, nhưng không phải tất cả các ngôn ngữ đều làm được (đáng chú ý nhất là: Java). May mắn thay, có một giải pháp thay thế đôi khi sẽ hoạt động:
String correctPattern = "[.]";
Đối với một công cụ regex, \.
và [.]
có nghĩa chính xác như vậy. Lưu ý rằng điều này không hoạt động trong mọi trường hợp, như dòng mới ( \\n
), dấu ngoặc vuông mở ( \\[
) và dấu gạch chéo ngược ( \\\\
hoặc [\\]
).
Lưu ý về Số phù hợp
(Gợi ý: Khó hơn bạn nghĩ)
Đối sánh một số là một trong những điều bạn nghĩ là khá dễ dàng với regex, nhưng thực ra nó khá phức tạp. Hãy xem xét cách tiếp cận của bạn, từng phần một:
[-+]?
Khớp một tùy chọn -
hoặc+
[0-9]*
Khớp 0 hoặc nhiều chữ số liên tiếp
\.?
Khớp một tùy chọn .
[0-9]*
Khớp 0 hoặc nhiều chữ số liên tiếp
Đầu tiên, chúng ta có thể làm sạch biểu thức này một chút bằng cách sử dụng viết tắt lớp ký tự cho các chữ số (lưu ý rằng điều này cũng dễ gặp phải vấn đề thoát được đề cập ở trên):
[0-9]
= \d
Tôi sẽ sử dụng \d
bên dưới, nhưng hãy nhớ rằng nó có nghĩa tương tự như [0-9]
. (Thực ra, trong một số công cụ \d
sẽ khớp với các chữ số từ tất cả các tập lệnh, vì vậy nó sẽ khớp nhiều hơn [0-9]
ý muốn, nhưng điều đó có lẽ không quan trọng trong trường hợp của bạn.)
Bây giờ, nếu bạn nhìn vào điều này một cách cẩn thận, bạn sẽ nhận ra rằng mọi phần nhỏ nhất của mẫu của bạn là tùy chọn . Mẫu này có thể khớp với một chuỗi có độ dài 0; một chuỗi chỉ bao gồm +
hoặc -
; hoặc, một chuỗi chỉ bao gồm a .
. Đây có lẽ không phải là những gì bạn dự định.
Để khắc phục điều này, sẽ hữu ích khi bắt đầu bằng cách "neo" regex của bạn với chuỗi bắt buộc tối thiểu, có thể là một chữ số:
\d+
Bây giờ chúng tôi muốn thêm phần thập phân, nhưng nó không đi đến nơi bạn nghĩ có thể:
\d+\.?\d* /* This isn't quite correct. */
Điều này vẫn sẽ khớp với các giá trị như 123.
. Tệ hơn nữa, nó có một chút gì đó xấu xa về nó. Khoảng thời gian là tùy chọn, có nghĩa là bạn có hai lớp lặp lại cạnh nhau ( \d+
và \d*
). Điều này thực sự có thể nguy hiểm nếu được sử dụng sai cách, mở hệ thống của bạn trước các cuộc tấn công DoS.
Để khắc phục điều này, thay vì coi dấu chấm là tùy chọn, chúng ta cần xử lý nó theo yêu cầu (để tách các lớp ký tự lặp lại) và thay vào đó làm cho toàn bộ phần thập phân là tùy chọn:
\d+(\.\d+)? /* Better. But... */
Điều này trông tốt hơn bây giờ. Chúng tôi yêu cầu khoảng thời gian giữa dãy chữ số đầu tiên và dãy số thứ hai, nhưng có một lỗ hổng nghiêm trọng: chúng tôi không thể đối sánh .123
vì chữ số hàng đầu hiện là bắt buộc.
Điều này thực sự khá dễ dàng để sửa chữa. Thay vì làm cho phần "thập phân" của số là tùy chọn, chúng ta cần xem nó như một chuỗi các ký tự: 1 hoặc nhiều số có thể được bắt đầu bởi một số .
có thể được bắt đầu bằng 0 hoặc nhiều số:
(\d*\.)?\d+
Bây giờ chúng ta chỉ cần thêm dấu:
[+-]?(\d*\.)?\d+
Tất nhiên, những dấu gạch chéo đó khá khó chịu trong Java, vì vậy chúng ta có thể thay thế trong các lớp ký tự dạng dài của mình:
[+-]?([0-9]*[.])?[0-9]+
Đối sánh so với Xác thực
Điều này đã xuất hiện trong các bình luận một vài lần, vì vậy tôi đang thêm một phụ lục về việc so khớp và xác thực.
Mục tiêu của việc đối sánh là tìm một số nội dung trong đầu vào ("cái kim trong bọc hay"). Mục tiêu của việc xác thực là để đảm bảo rằng đầu vào có định dạng mong đợi.
Regexes, về bản chất, chỉ khớp với văn bản. Với một số đầu vào, họ sẽ tìm thấy một số văn bản phù hợp hoặc sẽ không. Tuy nhiên, bằng cách "gắn" một biểu thức vào đầu và cuối của đầu vào bằng các thẻ liên kết ( ^
và $
), chúng tôi có thể đảm bảo rằng không tìm thấy kết quả khớp nào trừ khi toàn bộ đầu vào khớp với biểu thức, sử dụng hiệu quả regex để xác thực .
Regex được mô tả ở trên ( [+-]?([0-9]*[.])?[0-9]+
) sẽ khớp với một hoặc nhiều số trong một chuỗi mục tiêu. Vì vậy, với đầu vào:
apple 1.34 pear 7.98 version 1.2.3.4
Regex sẽ phù hợp 1.34
, 7.98
, 1.2
, .3
và .4
.
Để xác thực rằng một đầu vào nhất định là một số và không có gì khác ngoài một số, hãy "gắn" biểu thức vào đầu và cuối của đầu vào bằng cách gói nó trong các thẻ liên kết:
^[+-]?([0-9]*[.])?[0-9]+$
Thao tác này sẽ chỉ tìm thấy kết quả phù hợp nếu toàn bộ dữ liệu đầu vào là một số dấu phẩy động và sẽ không tìm thấy kết quả phù hợp nếu đầu vào chứa các ký tự bổ sung. Vì vậy, với đầu vào 1.2
, một kết quả phù hợp sẽ được tìm thấy, nhưng apple 1.2 pear
không tìm thấy kết quả phù hợp nào.
Lưu ý rằng một số công cụ regex có một validate
, isMatch
hoặc chức năng tương tự, trong đó chủ yếu làm những gì tôi đã mô tả tự động, trở về true
nếu kết hợp được tìm thấy và false
nếu không phù hợp được tìm thấy. Cũng nên nhớ rằng một số công cụ cho phép bạn đặt các cờ thay đổi định nghĩa của ^
và $
, khớp với đầu / cuối của một dòng chứ không phải là đầu / cuối của toàn bộ đầu vào. Đây thường không phải là mặc định, nhưng hãy đề phòng những cờ này.