Apakah ada fungsi RegExp.escape di Javascript?

443

Saya hanya ingin membuat ekspresi reguler dari string yang mungkin.

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

Apakah ada metode bawaan untuk itu? Jika tidak, apa yang digunakan orang? Ruby punya RegExp.escape. Saya tidak merasa perlu menulis sendiri, pasti ada sesuatu yang standar di luar sana. Terima kasih!

Lance Pollard
sumber
15
Hanya ingin memberi tahu Anda orang-orang baik yang RegExp.escapesedang bekerja dan siapa pun yang berpikir mereka memiliki input yang berharga sangat dipersilakan untuk berkontribusi. core-js dan polyfill lain menawarkannya.
Benjamin Gruenbaum
5
Menurut pembaruan terbaru dari jawaban ini, proposal ini ditolak: Lihat masalah
coba-coba-akhirnya

Jawaban:

574

Fungsi yang ditautkan di atas tidak cukup. Gagal melarikan diri ^atau $(mulai dan akhir string), atau -, yang dalam grup karakter digunakan untuk rentang.

Gunakan fungsi ini:

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

Meskipun mungkin tampak tidak perlu pada pandangan pertama, melarikan diri -(dan juga ^) membuat fungsi yang cocok untuk melarikan diri karakter yang akan dimasukkan ke dalam kelas karakter serta tubuh regex.

Lolos / membuat fungsi yang cocok untuk melarikan diri karakter yang akan digunakan dalam JS regex literal untuk eval nanti.

Karena tidak ada kerugian untuk melarikan diri dari keduanya, masuk akal untuk melarikan diri untuk membahas kasus penggunaan yang lebih luas.

Dan ya, gagal mengecewakan karena ini bukan bagian dari JavaScript standar.

bobince
sumber
16
sebenarnya, kita tidak perlu melarikan diri /sama sekali
durï
28
@ Paul: Perl quotemeta( \Q), Python re.escape, PHP preg_quote, Ruby Regexp.quote...
bobince
13
Jika Anda akan menggunakan fungsi ini dalam satu lingkaran, mungkin yang terbaik untuk membuat objek RegExp itu variabel sendiri var e = /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g;dan kemudian fungsi Anda adalah return s.replace(e, '\\$&');Dengan cara ini Anda hanya instantiate RegExp sekali.
styfle
15
Argumen standar terhadap penambahan objek bawaan berlaku di sini, bukan? Apa yang terjadi jika versi ECMAScript di masa depan menyediakan RegExp.escapeimplementasinya yang berbeda dengan Anda? Bukankah lebih baik fungsi ini tidak melekat pada apa pun?
Mark Amery
15
bobince tidak peduli untuk pendapat
eslint
115

Bagi siapa pun yang menggunakan lodash, karena v3.0.0 fungsi _.escapeRegExp sudah ada di dalamnya :

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

Dan, jika Anda tidak ingin memerlukan perpustakaan lodash lengkap, Anda mungkin hanya memerlukan fungsi itu !

gustavohenke
sumber
6
bahkan ada paket npm hanya ini! npmjs.com/package/lodash.escaperegexp
Ted Pennings
1
Ini mengimpor banyak kode yang benar-benar tidak perlu ada di sana untuk hal yang sederhana. Gunakan jawaban bobince ... bekerja untuk saya dan byte yang jauh lebih sedikit untuk memuat daripada versi lodash!
Rob Evans
6
@RobEvans jawaban saya dimulai dengan "Untuk siapa saja yang menggunakan lodash" , dan saya bahkan menyebutkan bahwa Anda dapat meminta hanya satu escapeRegExpfungsi.
gustavohenke
2
@ gustavohenke Maaf saya seharusnya sedikit lebih jelas, saya menyertakan modul yang ditautkan dalam "fungsi itu" dan itulah yang saya komentari. Jika Anda melihatnya cukup banyak kode untuk apa yang seharusnya secara efektif menjadi fungsi tunggal dengan satu regexp di dalamnya. Setuju jika Anda sudah menggunakan lodash maka masuk akal untuk menggunakannya, tetapi sebaliknya gunakan jawaban yang lain. Maaf atas komentar yang tidak jelas.
Rob Evans
2
@ maddob Saya tidak bisa melihat bahwa \ x3 yang Anda sebutkan: string saya yang lolos terlihat bagus, persis seperti yang saya harapkan
Federico Fissore
43

Sebagian besar ekspresi di sini menyelesaikan kasus penggunaan tunggal spesifik.

Tidak apa-apa, tapi saya lebih suka pendekatan "selalu berhasil".

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

Ini akan "sepenuhnya keluar" dari string literal untuk penggunaan berikut dalam ekspresi reguler:

  • Penyisipan dalam ekspresi reguler. Misalnyanew RegExp(regExpEscape(str))
  • Penyisipan dalam kelas karakter. Misalnyanew RegExp('[' + regExpEscape(str) + ']')
  • Penyisipan dalam specifier jumlah integer. Misalnyanew RegExp('x{1,' + regExpEscape(str) + '}')
  • Eksekusi di mesin ekspresi reguler non-JavaScript.

Karakter Khusus yang Dicakup:

  • -: Membuat rentang karakter dalam kelas karakter.
  • [/ ]: Memulai / mengakhiri kelas karakter.
  • {/ }: Memulai / mengakhiri specifier angka.
  • (/ ): Memulai / mengakhiri grup.
  • */ +/ ?: Menentukan jenis pengulangan.
  • .: Cocok dengan karakter apa pun.
  • \: Melepaskan karakter, dan memulai entitas.
  • ^: Menentukan mulai dari zona pencocokan, dan meniadakan pencocokan di kelas karakter.
  • $: Menentukan akhir zona pencocokan.
  • |: Menentukan pergantian.
  • #: Menentukan komentar dalam mode jarak bebas.
  • \s: Diabaikan dalam mode jarak bebas.
  • ,: Memisahkan nilai dalam penentu angka.
  • /: Memulai atau mengakhiri ekspresi.
  • :: Menyelesaikan tipe grup khusus, dan bagian dari kelas karakter Perl-style.
  • !: Meniadakan grup dengan lebar nol.
  • </ =: Bagian dari spesifikasi grup nol-lebar.

Catatan:

  • /tidak sepenuhnya diperlukan dalam rasa ekspresi reguler. Namun, itu melindungi jika seseorang (gemetaran) tidak eval("/" + pattern + "/");.
  • , memastikan bahwa jika string dimaksudkan sebagai bilangan bulat dalam penentu angka, itu akan menyebabkan kesalahan kompilasi RegExp alih-alih diam-diam mengkompilasi yang salah.
  • #, dan \stidak perlu melarikan diri dalam JavaScript, tetapi lakukan dalam banyak rasa lainnya. Mereka melarikan diri di sini kalau-kalau ekspresi reguler nanti akan diteruskan ke program lain.

Jika Anda juga perlu untuk membuktikan di masa depan ekspresi reguler terhadap potensi penambahan kemampuan mesin regex JavaScript, saya sarankan menggunakan yang lebih paranoid:

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

Fungsi ini lolos dari setiap karakter kecuali yang dijamin secara eksplisit tidak akan digunakan untuk sintaks dalam rasa ekspresi reguler di masa mendatang.


Untuk yang benar-benar sanitasi, pertimbangkan kasus tepi ini:

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

Ini harus dikompilasi dengan baik dalam JavaScript, tetapi tidak dalam beberapa rasa lainnya. Jika ingin beralih ke rasa lain, null case s === ''harus diperiksa secara independen, seperti:

var s = '';
new RegExp('(choice1|choice2' + (s ? '|' + regExpEscape(s) : '') + ')');
Pi Marillion
sumber
1
Tidak /perlu melarikan diri di [...]kelas karakter.
Dan Dascalescu
1
Sebagian besar dari ini tidak perlu melarikan diri. "Membuat rentang karakter dalam kelas karakter" - Anda tidak pernah berada dalam kelas karakter di dalam string. "Menentukan komentar dalam mode jarak bebas, Diabaikan dalam mode jarak bebas" - tidak didukung dalam javascript. "Memisahkan nilai dalam penentu angka" - Anda tidak pernah dalam penentu angka di dalam string. Anda juga tidak dapat menulis teks arbitrer di dalam spesifikasi penamaan. "Mulai atau berakhir ekspresi" - tidak perlu melarikan diri. Eval bukan kasus, karena akan membutuhkan lebih banyak melarikan diri. [akan dilanjutkan di komentar berikutnya]
Qwertiy
"Menyelesaikan tipe grup khusus, dan bagian dari kelas karakter Perl-style" - tampaknya tidak tersedia di javascript. "Meniadakan grup lebar nol, Bagian dari spesifikasi grup lebar nol" - Anda tidak pernah memiliki grup di dalam string.
Qwertiy
@ Qwertiy Alasan untuk lolos ekstra ini adalah untuk menghilangkan kasus tepi yang dapat menyebabkan masalah dalam kasus penggunaan tertentu. Misalnya, pengguna fungsi ini mungkin ingin memasukkan string regex yang diloloskan ke regex lain sebagai bagian dari grup, atau bahkan untuk digunakan dalam bahasa lain selain Javascript. Fungsi tidak membuat asumsi seperti "Saya tidak akan pernah menjadi bagian dari kelas karakter", karena itu dimaksudkan untuk menjadi umum . Untuk pendekatan lebih lanjut YAGNI, lihat salah satu jawaban lain di sini.
Pi Marillion
Baik sekali. Mengapa _ tidak luput? Apa yang memastikan itu mungkin tidak akan menjadi sintaks regex nanti?
madprops
30

Panduan Jaringan Pengembang Mozilla untuk Ekspresi Reguler menyediakan fungsi pelolosan ini:

function escapeRegExp(string) {
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
tenang
sumber
@DanDascalescu Anda benar. Halaman MDN telah diperbarui dan =tidak lagi disertakan.
quietmint
21

Dalam widget autocomplete jQueryUI (versi 1.9.1) mereka menggunakan regex yang sedikit berbeda (Line 6753), inilah ekspresi reguler yang dikombinasikan dengan pendekatan @bobince.

RegExp.escape = function( value ) {
     return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
}
Pierluc SS
sumber
4
Satu-satunya perbedaan adalah bahwa mereka lolos ,(yang bukan merupakan metacharacter), dan #dan spasi putih yang hanya penting dalam mode spasi bebas (yang tidak didukung oleh JavaScript). Namun, mereka melakukannya dengan benar agar tidak luput dari tebasan ke depan.
Martin Ender
18
Jika Anda ingin menggunakan kembali implementasi UI jquery daripada menempelkan kode secara lokal, ikuti $.ui.autocomplete.escapeRegex(myString).
Scott Stafford
2
lodash juga memiliki ini, _. escapeRegExp dan npmjs.com/package/lodash.escaperegexp
Ted Pennings
v1.12 sama, ok!
Peter Krauss
13

Tidak ada yang mencegah Anda dari melarikan diri setiap karakter non-alfanumerik:

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

Anda kehilangan tingkat keterbacaan tertentu ketika melakukannya re.toString()tetapi Anda memenangkan banyak kesederhanaan (dan keamanan).

Menurut ECMA-262, di satu sisi, ekspresi reguler "sintaks karakter" selalu non-alfanumerik, sehingga hasilnya adalah aman, dan escape sequence khusus ( \d, \w, \n) selalu alfanumerik sehingga tidak ada lolos kontrol palsu akan diproduksi .

filip
sumber
Sederhana dan efektif. Saya suka ini jauh lebih baik daripada jawaban yang diterima. Untuk (sebenarnya) browser lama, .replace(/[^\w]/g, '\\$&')akan bekerja dengan cara yang sama.
Tomas Langkaas
6
Ini gagal dalam mode Unicode. Misalnya, new RegExp('🍎'.replace(/(?=\W)/g, '\\'), 'u')melempar pengecualian karena \Wcocok dengan masing-masing unit kode pasangan pengganti secara terpisah, sehingga menghasilkan kode melarikan diri yang tidak valid.
Alexey Lebedev
1
alternatif:.replace(/\W/g, "\\$&");
Miguel Pynto
@AlexeyLebedev. Apakah jawabannya sudah diperbaiki untuk menangani mode Unicode? Atau adakah solusi di tempat lain yang melakukannya, sambil mempertahankan kesederhanaan ini?
Johnny mengapa
6

Ini adalah versi yang lebih pendek.

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

Ini termasuk karakter non-meta dari %, &, ', dan ,, namun spesifikasi JavaScript RegExp memungkinkan ini.

kzh
sumber
2
Saya tidak akan menggunakan versi "lebih pendek" ini, karena rentang karakter menyembunyikan daftar karakter, yang membuatnya lebih sulit untuk memverifikasi kebenaran pada pandangan pertama.
nhahtdh
@nhahtdh Saya mungkin juga tidak, tetapi diposting di sini untuk informasi.
kzh
@ kzh: memposting "untuk informasi" membantu kurang dari memposting untuk memahami. Tidakkah Anda setuju bahwa jawaban saya lebih jelas?
Dan Dascalescu
Setidaknya, .tidak terjawab. Dan (). Atau tidak? [-^aneh. Saya tidak ingat apa yang ada di sana.
Qwertiy
Itu berada dalam kisaran yang ditentukan.
kzh
3

Daripada hanya melarikan diri karakter yang akan menyebabkan masalah dalam ekspresi reguler Anda (misalnya: daftar hitam), mengapa tidak mempertimbangkan menggunakan daftar putih saja. Dengan cara ini setiap karakter dianggap ternoda kecuali cocok.

Untuk contoh ini, asumsikan ungkapan berikut:

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

Ini daftar putih huruf, angka dan spasi:

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

Pengembalian:

"be \|\| \! be"

Ini mungkin lolos dari karakter yang tidak perlu melarikan diri, tetapi ini tidak menghalangi ekspresi Anda (mungkin beberapa hukuman waktu kecil - tapi itu layak untuk keselamatan).

bashaus
sumber
Apakah ini berbeda dari jawaban @ filip? stackoverflow.com/a/40562456/209942
johny mengapa
3
escapeRegExp = function(str) {
  if (str == null) return '';
  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
};
Ravi Gadhia
sumber
1

Fungsi-fungsi dalam jawaban lain terlalu banyak untuk melarikan diri dari seluruh ekspresi reguler (mereka mungkin berguna untuk keluar dari bagian ekspresi reguler yang nantinya akan digabungkan ke dalam regexps yang lebih besar).

Jika Anda melarikan diri seluruh regexp dan selesai dengan hal itu, mengutip metakarakter yang baik standalone ( ., ?, +, *, ^, $, |, \) atau memulai sesuatu ( (, [, {) adalah semua yang Anda perlu:

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

Dan ya, mengecewakan bahwa JavaScript tidak memiliki fungsi seperti bawaan ini.

Dan Dascalescu
sumber
Katakanlah Anda lolos dari input pengguna (text)nextdan masukkan: (?:+ input + ). Metode Anda akan memberikan string (?:\(text)next)yang dihasilkan yang gagal dikompilasi. Perhatikan bahwa ini adalah penyisipan yang cukup masuk akal, bukan yang gila seperti re\+ input + re(dalam hal ini, programmer dapat disalahkan karena melakukan sesuatu yang bodoh)
nhahtdh
1
@nhahtdh: jawaban saya secara khusus disebutkan lolos dari seluruh ekspresi reguler dan "sedang dilakukan" dengan mereka, bukan bagian (atau bagian masa depan) dari regexps. Silakan membatalkan downvote?
Dan Dascalescu
Ini jarang terjadi bahwa Anda akan lolos dari seluruh ekspresi - ada operasi string, yang jauh lebih cepat dibandingkan dengan regex jika Anda ingin bekerja dengan string literal.
nhahtdh
Ini tidak menyebutkan bahwa itu salah - \harus diloloskan, karena regex Anda akan \wtetap utuh. Selain itu, JavaScript tampaknya tidak mengizinkan penelusuran ), setidaknya itulah yang menyebabkan kesalahan Firefox.
nhahtdh
1
Mohon )
ceritakan
1

Pendekatan lain (jauh lebih aman) adalah keluar dari semua karakter (dan bukan hanya beberapa karakter khusus yang saat ini kita kenal) menggunakan format escape 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}'

Harap dicatat bahwa Anda harus melewati ubendera agar metode ini berfungsi:

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

Hanya ada dan akan pernah ada 12 meta karakter yang perlu dilepaskan
untuk dianggap literal.

Tidak masalah apa yang dilakukan dengan string yang lolos, dimasukkan ke dalam
pembungkus regex yang seimbang , ditambahkan, tidak masalah.

Lakukan penggantian string menggunakan ini

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

sumber
bagaimana ]?
Thomasleveil