Có chức năng RegExp.escape trong Javascript không?


442

Tôi chỉ muốn tạo một biểu thức chính quy từ bất kỳ chuỗi nào có thể.

var usersString = "Hello?!*`~World()[]";
var expression = new RegExp(RegExp.escape(usersString))
var matches = "Hello".match(expression);

Có một phương pháp được xây dựng cho điều đó? Nếu không, mọi người sử dụng cái gì? Ruby có RegExp.escape. Tôi không cảm thấy mình cần phải tự viết, phải có một cái gì đó tiêu chuẩn ngoài kia. Cảm ơn!


15
Chỉ muốn cập nhật cho bạn những người tốt RegExp.escapehiện đang làm việc và bất cứ ai nghĩ rằng họ có đầu vào có giá trị đều rất sẵn lòng đóng góp. core-js và các polyfill khác cung cấp nó.
Benjamin Gruenbaum

Câu trả lời:


573

Các chức năng liên kết ở trên là không đủ. Nó không thoát ^hoặc $(bắt đầu và kết thúc chuỗi), hoặc -, trong một nhóm ký tự được sử dụng cho các phạm vi.

Sử dụng chức năng này:

function escapeRegex(string) {
    return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}

Mặc dù thoạt nhìn có vẻ không cần thiết, việc thoát -(cũng như ^) làm cho chức năng phù hợp để thoát các ký tự được chèn vào một lớp nhân vật cũng như cơ thể của biểu thức chính quy.

Trốn thoát /làm cho hàm phù hợp để thoát các ký tự được sử dụng trong một biểu thức chính tả của JS cho eval sau này.

Vì không có nhược điểm nào để thoát khỏi một trong hai, nên thoát ra để bao quát các trường hợp sử dụng rộng hơn.

Và vâng, thật đáng thất vọng khi đây không phải là một phần của JavaScript tiêu chuẩn.


16
trên thực tế, chúng ta không cần phải thoát khỏi /ở tất cả
gai

28
@Paul: Perl quotemeta( \Q), Python re.escape, PHP preg_quote, Ruby Regexp.quote...
bobince

13
Nếu bạn định sử dụng hàm này trong một vòng lặp, có lẽ tốt nhất là biến đối tượng RegExp thành biến riêng var e = /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g;và sau đó chức năng của bạn là return s.replace(e, '\\$&');Cách này bạn chỉ khởi tạo RegExp một lần.
styfle

15
Đối số tiêu chuẩn chống lại việc tăng cường các đối tượng tích hợp được áp dụng ở đây, phải không? Điều gì xảy ra nếu một phiên bản tương lai của ECMAScript cung cấp cho RegExp.escapengười thực hiện khác với bạn? Sẽ không tốt hơn khi chức năng này không được gắn vào bất cứ điều gì?
Mark Amery

15
bobince quan tâm không phải vì ý kiến ​​của eslint
bobince

113

Đối với bất cứ ai sử dụng lodash, kể từ v3.0.0 một _.escapeRegExp chức năng được xây dựng-in:

_.escapeRegExp('[lodash](https://lodash.com/)');
// → '\[lodash\]\(https:\/\/lodash\.com\/\)'

Và, trong trường hợp bạn không muốn yêu cầu thư viện lodash đầy đủ, bạn có thể chỉ yêu cầu chức năng đó !


6
thậm chí còn có một gói npm chỉ thế này! npmjs.com/package/lodash.escaperegapi
Ted Pennings

1
Điều này nhập vô số mã mà thực sự không cần phải có cho một điều đơn giản như vậy. Sử dụng câu trả lời của bobince ... hoạt động với tôi và rất nhiều byte để tải hơn phiên bản lodash!
Rob Evans

6
@RobEvans bắt đầu câu trả lời của tôi với "Đối với bất cứ ai sử dụng lodash" , và tôi thậm chí đề cập rằng bạn có thể yêu cầu chỉ các escapeRegExpchức năng.
gustavohenke

2
@gustavohenke Xin lỗi tôi nên rõ ràng hơn một chút, tôi đã bao gồm mô-đun được liên kết đến trong "chỉ chức năng đó" của bạn và đó là những gì tôi đã nhận xét. Nếu bạn xem nó có khá nhiều mã cho một chức năng duy nhất có hiệu quả với một biểu thức chính quy trong đó. Đồng ý nếu bạn đã sử dụng lodash thì sẽ hợp lý khi sử dụng nó, nhưng nếu không thì sử dụng câu trả lời khác. Xin lỗi vì nhận xét không rõ ràng.
Rob Evans

2
@maddob Tôi không thể thấy rằng \ x3 bạn đã đề cập: chuỗi thoát của tôi trông rất tốt, đúng như những gì tôi mong đợi
Federico Fissore

43

Hầu hết các biểu thức ở đây giải quyết các trường hợp sử dụng cụ thể duy nhất.

Điều đó không sao, nhưng tôi thích cách tiếp cận "luôn hoạt động".

function regExpEscape(literal_string) {
    return literal_string.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g, '\\$&');
}

Điều này sẽ "thoát hoàn toàn" một chuỗi ký tự cho bất kỳ cách sử dụng nào sau đây trong các biểu thức thông thường:

  • Chèn trong một biểu thức chính quy. Ví dụnew RegExp(regExpEscape(str))
  • Chèn trong một lớp nhân vật. Ví dụnew RegExp('[' + regExpEscape(str) + ']')
  • Chèn vào specifier số nguyên. Ví dụnew RegExp('x{1,' + regExpEscape(str) + '}')
  • Thực thi trong các công cụ biểu thức chính quy không phải là JavaScript.

Nhân vật đặc biệt được bảo hiểm:

  • -: Tạo một phạm vi nhân vật trong một lớp nhân vật.
  • [/ ]: Bắt đầu / kết thúc một lớp nhân vật.
  • {/ }: Bắt đầu / kết thúc một công cụ xác định số.
  • (/ ): Bắt đầu / kết thúc một nhóm.
  • */ +/ ?: Chỉ định loại lặp lại.
  • .: Phù hợp với bất kỳ nhân vật.
  • \: Thoát khỏi các ký tự và bắt đầu các thực thể.
  • ^: Chỉ định bắt đầu vùng khớp và phủ định khớp trong một lớp ký tự.
  • $: Chỉ định kết thúc vùng khớp.
  • |: Chỉ định xen kẽ.
  • #: Chỉ định nhận xét trong chế độ giãn cách miễn phí.
  • \s: Bỏ qua trong chế độ khoảng cách miễn phí.
  • ,: Tách các giá trị trong bộ xác định số.
  • /: Bắt đầu hoặc kết thúc biểu thức.
  • :: Hoàn thành các loại nhóm đặc biệt và một phần của các lớp nhân vật theo phong cách Perl.
  • !: Phủ định nhóm độ rộng bằng không.
  • </ =: Một phần của thông số kỹ thuật nhóm không độ rộng.

Ghi chú:

  • /là không thực sự cần thiết trong bất kỳ hương vị của biểu thức thông thường. Tuy nhiên, nó bảo vệ trong trường hợp ai đó (rùng mình) làm eval("/" + pattern + "/");.
  • , đảm bảo rằng nếu chuỗi có nghĩa là một số nguyên trong bộ xác định số, thì nó sẽ gây ra lỗi biên dịch RegExp đúng cách thay vì âm thầm biên dịch sai.
  • #\skhông cần phải thoát trong JavaScript, nhưng thực hiện theo nhiều hương vị khác. Chúng được thoát ở đây trong trường hợp biểu thức chính quy sau đó sẽ được chuyển sang chương trình khác.

Nếu bạn cũng cần chứng minh tương lai biểu thức chính quy chống lại các bổ sung tiềm năng cho các khả năng của công cụ regex JavaScript, tôi khuyên bạn nên sử dụng phép hoang tưởng hơn:

function regExpEscapeFuture(literal_string) {
    return literal_string.replace(/[^A-Za-z0-9_]/g, '\\$&');
}

Hàm này thoát khỏi mọi ký tự trừ những ký tự được bảo đảm rõ ràng không được sử dụng cho cú pháp trong các hương vị biểu thức chính quy trong tương lai.


Để thực sự vệ sinh, hãy xem xét trường hợp cạnh này:

var s = '';
new RegExp('(choice1|choice2|' + regExpEscape(s) + ')');

Điều này sẽ biên dịch tốt trong JavaScript, nhưng sẽ không có trong một số hương vị khác. Nếu có ý định chuyển sang hương vị khác, trường hợp null s === ''nên được kiểm tra độc lập, như vậy:

var s = '';
new RegExp('(choice1|choice2' + (s ? '|' + regExpEscape(s) : '') + ')');

1
Các /không cần phải được thoát trong [...]lớp nhân vật.
Dan Dascalescu

1
Hầu hết trong số này không cần phải thoát. "Tạo một phạm vi ký tự trong một lớp ký tự" - bạn không bao giờ ở trong một lớp ký tự bên trong chuỗi. "Chỉ định nhận xét trong chế độ giãn cách miễn phí, Bỏ qua ở chế độ giãn cách miễn phí" - không được hỗ trợ trong javascript. "Tách các giá trị trong bộ xác định số" - bạn không bao giờ nằm ​​trong bộ xác định số bên trong chuỗi. Ngoài ra, bạn không thể viết văn bản tùy ý bên trong đặc tả tên. "Bắt đầu hoặc kết thúc biểu thức" - không cần phải thoát. Eval không phải là một trường hợp, vì nó sẽ yêu cầu thoát nhiều hơn. [sẽ được tiếp tục trong phần bình luận tiếp theo]
Qwertiy

"Hoàn thành các loại nhóm đặc biệt và một phần của các lớp nhân vật theo phong cách Perl" - dường như không có sẵn trong javascript. "Phủ định nhóm có độ rộng bằng không, Một phần của thông số nhóm có độ rộng bằng không" - bạn không bao giờ có các nhóm bên trong chuỗi.
Qwertiy

@Qwertiy Lý do cho những lần thoát thêm này là để loại bỏ các trường hợp cạnh có thể gây ra vấn đề trong một số trường hợp sử dụng. Chẳng hạn, người dùng chức năng này có thể muốn chèn chuỗi regex đã thoát vào một regex khác như một phần của một nhóm hoặc thậm chí để sử dụng trong một ngôn ngữ khác ngoài Javascript. Hàm này không đưa ra các giả định như "Tôi sẽ không bao giờ là một phần của lớp nhân vật", bởi vì nó có nghĩa là chung chung . Để biết thêm về cách tiếp cận YAGNI, hãy xem bất kỳ câu trả lời nào khác tại đây.
Pi Marillion

Rất tốt. Tại sao _ không thoát được mặc dù? Điều gì đảm bảo nó có thể sẽ không trở thành cú pháp regex sau này?
madprops


21

Trong tiện ích tự động hoàn thành của jQueryUI (phiên bản 1.9.1), họ sử dụng biểu thức chính quy hơi khác (Dòng 6753), đây là biểu thức chính quy kết hợp với phương pháp @bobince.

RegExp.escape = function( value ) {
     return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
}

4
Sự khác biệt duy nhất là chúng thoát ,(không phải là metacharacter) #và khoảng trắng chỉ quan trọng trong chế độ khoảng cách tự do (không được JavaScript hỗ trợ). Tuy nhiên, họ làm đúng để không thoát khỏi nhát chém về phía trước.
Martin Ender

18
Nếu bạn muốn sử dụng lại việc triển khai UI của jquery thay vì dán mã cục bộ, hãy đi cùng $.ui.autocomplete.escapeRegex(myString).
Scott Stafford

2
lodash cũng có cái này, _. escRegExp và npmjs.com/package/lodash.escaperegapi
Ted Pennings

v1.12 cũng vậy, ok!
Peter Krauss

13

Không có gì có thể ngăn bạn thoát khỏi mọi ký tự không phải là chữ và số:

usersString.replace(/(?=\W)/g, '\\');

Bạn mất một mức độ dễ đọc nhất định khi làm re.toString()nhưng bạn giành được rất nhiều sự đơn giản (và bảo mật).

Theo ECMA-262, một mặt, biểu thức chính quy "nhân vật cú pháp" luôn không tự chữ và số, như vậy mà kết quả là an toàn, và trình tự thoát đặc biệt ( \d, \w, \n) luôn tự chữ và số như vậy mà không thoát kiểm soát giả sẽ được sản xuất .


Đơn giản và hiệu quả. Tôi thích điều này tốt hơn nhiều so với câu trả lời được chấp nhận. Đối với (thực sự) các trình duyệt cũ, .replace(/[^\w]/g, '\\$&')sẽ hoạt động theo cùng một cách.
Tomas Langkaas

6
Điều này không thành công trong chế độ Unicode. Ví dụ, new RegExp('🍎'.replace(/(?=\W)/g, '\\'), 'u')ném ngoại lệ vì \Wkhớp từng đơn vị mã của một cặp thay thế, dẫn đến mã thoát không hợp lệ.
Alexey Lebedev

1
thay thế:.replace(/\W/g, "\\$&");
Miguel Pynto 21/03/18

@AlexeyLebedev Có câu trả lời đã được sửa để xử lý chế độ Unicode không? Hoặc có một giải pháp nào khác, trong khi vẫn duy trì sự đơn giản này?
johny tại sao


6

Đây là một phiên bản ngắn hơn.

RegExp.escape = function(s) {
    return s.replace(/[$-\/?[-^{|}]/g, '\\$&');
}

Điều này bao gồm các nhân vật phi meta của %, &, ', và ,, nhưng thông số kỹ thuật Javascript RegExp phép này.


2
Tôi sẽ không sử dụng phiên bản "ngắn hơn" này, vì phạm vi ký tự ẩn danh sách các ký tự, điều này khiến việc xác minh tính chính xác ngay từ cái nhìn đầu tiên trở nên khó khăn hơn.
nhahtdh

@nhahtdh Có lẽ tôi cũng không, nhưng nó được đăng ở đây để biết thông tin.
kzh

@kzh: đăng bài "để biết thông tin" giúp ít hơn đăng bài để hiểu. Bạn sẽ không đồng ý rằng câu trả lời của tôi rõ ràng hơn?
Dan Dascalescu

Ít nhất, .là bỏ lỡ. Và (). Hay không? [-^lạ. Tôi không nhớ những gì ở đó.
Qwertiy

Đó là trong phạm vi được chỉ định.
kzh


3

Thay vì chỉ thoát các ký tự sẽ gây ra sự cố trong biểu thức thông thường của bạn (ví dụ: danh sách đen), tại sao không xem xét sử dụng danh sách trắng thay thế. Bằng cách này, mỗi nhân vật được coi là vô vị trừ khi nó phù hợp.

Trong ví dụ này, giả sử biểu thức sau:

RegExp.escape('be || ! be');

Danh sách trắng này chữ, số và dấu cách:

RegExp.escape = function (string) {
    return string.replace(/([^\w\d\s])/gi, '\\$1');
}

Trả về:

"be \|\| \! be"

Điều này có thể thoát khỏi các ký tự không cần phải thoát, nhưng điều này không cản trở biểu hiện của bạn (có thể là một số hình phạt nhỏ trong thời gian - nhưng nó đáng để đảm bảo an toàn).


Câu trả lời của anh ấy có khác với câu trả lời của @ filip không? stackoverflow.com/a/40562456/209942
johny tại sao

3
escapeRegExp = function(str) {
  if (str == null) return '';
  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
};

1

Các hàm trong các câu trả lời khác là quá mức cần thiết để thoát toàn bộ biểu thức chính quy (chúng có thể hữu ích cho việc thoát các phần của biểu thức chính quy mà sau đó sẽ được nối vào các biểu thức lớn hơn).

Nếu bạn thoát khỏi toàn bộ một regexp và được thực hiện với nó, trích dẫn metacharacters rằng hoặc là độc lập ( ., ?, +, *, ^, $, |, \) hoặc bắt đầu một cái gì đó ( (, [, {) là tất cả các bạn cần:

String.prototype.regexEscape = function regexEscape() {
  return this.replace(/[.?+*^$|({[\\]/g, '\\$&');
};

Và vâng, thật đáng thất vọng khi JavaScript không có chức năng như tích hợp sẵn này.


Giả sử bạn thoát khỏi đầu vào của người dùng (text)nextvà chèn nó vào: (?:+ input + ). Phương thức của bạn sẽ đưa ra chuỗi kết quả (?:\(text)next)không biên dịch được. Lưu ý rằng đây là cách chèn khá hợp lý, không phải là một kiểu điên như re\+ input + re(trong trường hợp này, lập trình viên có thể bị đổ lỗi vì đã làm điều gì đó ngu ngốc)
nhahtdh

1
@nhahtdh: câu trả lời của tôi đặc biệt đề cập đến việc thoát khỏi toàn bộ các biểu thức chính quy và "được thực hiện" với chúng, không phải các phần (hoặc các phần trong tương lai) của biểu thức chính quy. Vui lòng hoàn tác các downvote?
Dan Dascalescu

Rất hiếm khi bạn thoát khỏi toàn bộ biểu thức - có hoạt động chuỗi, nhanh hơn nhiều so với regex nếu bạn muốn làm việc với chuỗi ký tự.
nhahtdh

Điều này không đề cập đến việc nó không chính xác - \nên được thoát, vì regex của bạn sẽ \wcòn nguyên vẹn. Ngoài ra, JavaScript dường như không cho phép theo dõi ), ít nhất đó là những gì Firefox gây ra lỗi.
nhahtdh

1
Vui lòng giải quyết phần về việc đóng cửa)
nhahtdh

1

Một cách tiếp cận khác (an toàn hơn nhiều) là thoát tất cả các ký tự (và không chỉ một vài ký tự đặc biệt mà chúng ta hiện biết) bằng cách sử dụng định dạng thoát unicode \u{code}:

function escapeRegExp(text) {
    return Array.from(text)
           .map(char => `\\u{${char.charCodeAt(0).toString(16)}}`)
           .join('');
}

console.log(escapeRegExp('a.b')); // '\u{61}\u{2e}\u{62}'

Xin lưu ý rằng bạn cần truyền ucờ cho phương thức này hoạt động:

var expression = new RegExp(escapeRegExp(usersString), 'u');

1

Chỉ có và đã từng có 12 nhân vật meta cần được trốn thoát
để được coi là một nghĩa đen.

Không có vấn đề gì được thực hiện với chuỗi thoát, được chèn vào
trình bao bọc regex cân bằng , được nối thêm, không thành vấn đề.

Thực hiện thay thế một chuỗi bằng cách này

var escaped_string = oldstring.replace( /[\\^$.|?*+()[{]/g, '\\$&' );

những gì về ]?
Thomasleveil
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.