Чи є у Javascript функція RegExp.escape?


442

Я просто хочу створити регулярний вираз з будь-якого можливого рядка.

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

Чи існує вбудований метод для цього? Якщо ні, то чим користуються люди? Рубі має RegExp.escape. Я не відчуваю, що мені потрібно писати своє, там повинно бути щось стандартне. Дякую!


15
Просто хотілося поповнити вас чудовим фольклором, над яким RegExp.escapeзараз працюють, і кожен, хто вважає, що має цінний внесок, дуже вітається. core-js та інші поліфіли пропонують це.
Бенджамін Груенбаум

Відповіді:


573

Функція, пов'язана вище, недостатня. Він не може вийти ^або $(початок і кінець рядка), або -, що в групі символів використовується для діапазонів.

Використовуйте цю функцію:

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

Хоча це може здатися непотрібним на перший погляд, втеча -(як і ^) робить функцію, підходящу для вставки втечі символів, як до класу символів, так і до тіла регулярного вираження.

Escaping /робить функцію, придатну для втечі символів, які будуть використані в прямому тексті JS регулярного виразів для подальшого оцінювання.

Оскільки уникнути жодного з них не існує недоліків, є сенс бігти, щоб охопити більш широкі випадки використання.

І так, прикро, що це не є частиною стандартного JavaScript.


16
на самому ділі, нам не потрібно бігти /на всіх
Thorn

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

13
Якщо ви збираєтеся використовувати цю функцію в циклі, можливо, найкраще зробити об'єкт RegExp його власною змінною, var e = /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g;і тоді ваша функція: return s.replace(e, '\\$&');Таким чином, ви інстанціюєте RegExp лише один раз.
styfle

15
Тут застосовуються стандартні аргументи проти розширення вбудованих об'єктів, ні? Що станеться, якщо майбутня версія ECMAScript передбачає, RegExp.escapeчия реалізація відрізняється від вашої? Чи не було б краще, щоб цю функцію ні до чого не прив’язували?
Марк Амері

15
bobince піклується не про думку
Еслінта

114

Для всіх, хто використовує lodash, оскільки v3.0.0 є вбудованою функцією _.escapeRegExp :

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

І якщо ви не хочете вимагати повної бібліотеки лодашів, вам може знадобитися саме ця функція !


6
є навіть пакет npm тільки цього! npmjs.com/package/lodash.escaperegexp
Тед Пеннінг

1
Це імпортує безліч кодів, які дійсно не повинні бути там для такої простої речі. Використовуйте відповідь bobince ... працює на мене і його стільки менше байтів для завантаження, ніж версія lodash!
Роб Еванс

6
@RobEvans моя відповідь починається з «Для тих , хто з допомогою lodash» , і я навіть згадувати , що ви можете вимагати тільки в escapeRegExpфункції.
gustavohenke

2
@gustavohenke Вибачте, я повинен був бути трохи більш зрозумілим, я включив модуль, пов'язаний з вашою "просто тією функцією", і це те, що я коментував. Якщо ви поглянете, це досить багато коду для того, що фактично повинно бути однією функцією з одним повторним виразом. Погодьтеся, якщо ви вже використовуєте лодаш, то є сенс використовувати його, але в іншому випадку використовуйте іншу відповідь. Вибачте за незрозумілий коментар.
Роб Еванс

2
@maddob Я не бачу, що \ x3 ви згадали: мої рятувальні струни виглядають добре, саме те, що я очікую
Федеріко Фіссоре

43

Більшість виразів тут вирішують окремі конкретні випадки використання.

Це нормально, але я віддаю перевагу підходу "завжди працює".

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

Це дозволить "повністю уникнути" буквального рядка для будь-якого з наступних застосувань у регулярних виразах:

  • Вставка в регулярний вираз. Напрnew RegExp(regExpEscape(str))
  • Вставка в клас персонажів. Напрnew RegExp('[' + regExpEscape(str) + ']')
  • Вставка в специфікатор цілого числа. Напрnew RegExp('x{1,' + regExpEscape(str) + '}')
  • Виконання в двигунах регулярного вираження без JavaScript.

Спеціальні персонажі охоплені:

  • -: Створює діапазон символів у класі символів.
  • [/ ]: Починає / закінчує клас символів.
  • {/ }: Починає / закінчує специфікатор нумерації.
  • (/ ): Починає / закінчує групу.
  • */ +/ ?: Вказує тип повторення.
  • .: Відповідає будь-якому символу.
  • \: Уникає символів та запускає об'єкти.
  • ^: Вказує початок зони узгодження та заперечує відповідність у класі символів.
  • $: Вказує кінець зони узгодження.
  • |: Вказує чергування.
  • #: Вказує коментар у режимі вільного інтервалу.
  • \s: Ігнорується в режимі вільного інтервалу.
  • ,: Відокремлює значення в специфікаторі числення.
  • /: Починає або закінчує вираз.
  • :: Завершує спеціальні типи груп та частина класів символів у стилі Perl.
  • !: Надає групу нульової ширини.
  • </ =: Частина специфікацій групи нульової ширини.

Примітки:

  • /не є строго необхідним в будь-якому ароматі регулярного вираження. Тим НЕ менше, він захищає в разі , якщо хто - то (тремтіння) робить eval("/" + pattern + "/");.
  • , гарантує, що якщо позначається, що рядок є цілим числом в числовому специфікаторі, вона належним чином спричинить помилку компіляції RegExp, а не мовчки скласти неправильно.
  • #, і \sне потрібно їх уникати в JavaScript, але в багатьох інших смаках. Вони уникають тут, якщо регулярний вираз згодом буде переданий іншій програмі.

Якщо вам також доведеться підтверджувати регулярне висловлення проти потенційних доповнень до можливостей двигуна регулярного виразу, рекомендую використовувати більш параноїдний:

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

Ця функція уникає будь-якого символу, за винятком тих, що явно гарантовано не використовуються для синтаксису в подальших смаках регулярного виразу.


По-справжньому захоплені санітарними умовами, розгляньте цей крайній випадок:

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

Це має складатись у JavaScript, але не в інших смаках. Якщо ви маєте намір перейти на інший аромат, нульовий випадок s === ''слід перевірити незалежно, як-от так:

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

1
/Не потрібно екранувати в [...]класі символів.
Дан Даскалеску

1
Більшість із них не потрібно уникати. "Створює діапазон символів у класі символів" - ви ніколи не знаходитесь у класі символів всередині рядка. "Вказує коментар у режимі вільного інтервалу, ігнорується у режимі вільного інтервалу" - не підтримується у JavaScript. "Відокремлює значення в специфікаторі числення" - ви ніколи не знаходитесь в специфікаторі чисельності всередині рядка. Також ви не можете писати довільний текст всередині специфікації імені. "Починає або закінчує вираз" - не потрібно бігти. Евал - це не випадок, оскільки це потребує набагато більше втечі. [буде продовжено у наступному коментарі]
Qwertiy

"Завершує спеціальні типи груп та частина класів символів у стилі Perl" - схоже, недоступна у javascript. "Непризначає групу нульової ширини, частина специфікацій групи нульової ширини" - у вас ніколи немає груп всередині рядка.
Qwertiy

@Qwertiy Причиною цих додаткових втеч є усунення крайових випадків, які можуть спричинити проблеми в певних випадках використання. Наприклад, користувач цієї функції може захотіти вставити рядок, що увійшов у регулярний вираз, в інший регулярний вираз як частину групи, або навіть для використання на іншій мові, крім Javascript. Функція не робить припущень на кшталт "Я ніколи не буду частиною класу символів", тому що це повинно бути загальним . Детальніше про підхід YAGNI дивіться будь-яку з інших відповідей тут.
Пі-мільйон

Дуже добре. Чому _ не втік? Що забезпечує це, ймовірно, пізніше не стане синтаксисом регулярних виразів?
madprops


21

У віджеті автозаповнення jQueryUI (версія 1.9.1) вони використовують дещо інший регулярний вираз (рядок 6753), ось регулярний вираз у поєднанні з підходом @bobince.

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

4
Різниця полягає лише в тому, що вони уникають ,(що не є метахарактером), а #також пробіли, які мають значення лише у режимі вільного інтервалу (який не підтримується JavaScript). Однак вони отримують право не уникати передньої косої риски.
Мартін Ендер

18
Якщо ви хочете повторно використовувати реалізацію інтерфейсу jquery, а не вставляти код локально, перейдіть з $.ui.autocomplete.escapeRegex(myString).
Скотт Стаффорд

2
Лодаш має і це, _. escapeRegExp і npmjs.com/package/lodash.escaperegexp
Тед Пеннінг

v1.12 те саме, добре!
Пітер Краус

13

Ніщо не повинно заважати вам просто уникнути кожного нелітерно-цифрового символу:

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

Ви втрачаєте певну читабельність при виконанні, re.toString()але ви виграєте велику простоту (та безпеку).

Згідно ECMA-262, з одного боку, регулярний вираз «синтаксичних символів» завжди НЕ алфавітно-цифровий, так що результат є безпечним, і спеціальні керуючі послідовності ( \d, \w, \n) завжди алфавітно - цифровий , такі , що ніякі помилкові вислизає управління не проводитиметься .


Простий і ефективний. Мені це подобається набагато краще, ніж прийнята відповідь. Для (дійсно) старих веб-переглядачів .replace(/[^\w]/g, '\\$&')вони працювали б так само.
Томаш Лангкаас

6
Це не вдалося в режимі Unicode. Наприклад, new RegExp('🍎'.replace(/(?=\W)/g, '\\'), 'u')виняток кидає, оскільки \Wвідповідає кожній одиниці коду сурогатної пари окремо, в результаті чого недійсні коди втечі.
Олексій Лебедєв

1
альтернатива:.replace(/\W/g, "\\$&");
Мігель Пінто

@AlexeyLebedev Чи було виправлено відповідь на обробку режиму Unicode? Або в іншому місці є рішення, яке робить, зберігаючи цю простоту?
Джоні, чому


6

Це коротша версія.

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

Це включає в себе НЕ-мета - символи %, &, ', і ,, але специфікація JavaScript RegExp дозволяє.


2
Я б не використовував цю "більш коротку" версію, оскільки діапазони символів приховують список символів, що ускладнює перевірку правильності на перший погляд.
nhahtdh

@nhahtdh Я, мабуть, і не хотів, але він розміщений тут для інформації.
кж

@kzh: публікація "для інформації" допомагає менше, ніж публікація для розуміння. Чи не погодилися б ви, що моя відповідь ясніша?
Дан Даскалеску

Принаймні, .пропущено. І (). Чи ні? [-^дивно. Я не пам'ятаю, що там.
Qwertiy

Вони знаходяться у визначеному діапазоні.
кж


3

Замість того, щоб не лише знаки, що утечуть, що спричинить проблеми у вашому регулярному вираженні (наприклад, чорний список), чому б не розглянути можливість використання білого списку. Таким чином, кожен персонаж вважається пошкодженим, якщо він не відповідає.

Для цього прикладу припустимо наступний вираз:

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

Це білі списки літер, цифр та пробілів:

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

Повернення:

"be \|\| \! be"

Це може уникнути символів, яких не потрібно уникати, але це не перешкоджає вашому самовираженню (можливо, деякі незначні часові штрафи - але це варто для безпеки).


Його це відрізняється від відповіді @ Філіпа? stackoverflow.com/a/40562456/209942
Johny чому


1

Функції в інших відповідях є надмірними для виходу з цілих регулярних виразів (вони можуть бути корисними для виходу з частин регулярних виразів, які згодом будуть об'єднані у більші регулярні вирази).

Якщо ви біжите все регулярний вираз і зробили з ним, посилаючись на метасимволу , які є або автономними ( ., ?, +, *, ^, $, |, \) або почати що - то ( (, [, {) є все , що вам потрібно:

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

І так, прикро, що JavaScript не має такої вбудованої функції.


Скажімо, ви уникаєте введення користувача (text)nextта вставляєте його у: (?:+ введення + ). Ваш метод дасть отриманий рядок, (?:\(text)next)який не вдасться компілювати. Зауважте, що це цілком розумна вставка, а не якась божевільна, як re\+ введення + re(у цьому випадку програміста можна звинуватити в тому, що він зробив щось дурне)
nhahtdh

1
@nhahtdh: моя відповідь конкретно згадувала про те, щоб уникнути цілих регулярних виразів і "робити з ними", а не частинами (або майбутніми частинами) регулярних виразів. Будь ласка, скасуйте поточний запис?
Дан Даскалеску

Рідко трапляється так, що ви б уникнули всього виразу - є рядкові операції, які набагато швидше порівняно з регулярними виразами, якщо ви хочете працювати з буквальним рядком.
nhahtdh

Це не зазначає, що це неправильно - \слід уникати, оскільки ваш регулярний вислів залишиться \wнедоторканим. Крім того, JavaScript, здається, не дозволяє робити трейлінг ), принаймні для цього Firefox видає помилку.
nhahtdh

1
Будь ласка, зверніться до частини про закриття)
nhahtdh

1

Інший (набагато безпечніший) підхід полягає в тому, щоб уникнути всіх персонажів (а не лише декількох спеціальних, про які ми знаємо в даний час), використовуючи формат втечі 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}'

Зауважте, що uдля цього методу потрібно передати прапор:

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

1

Існує лише коли-небудь і коли-небудь буде 12 мета-символів, яких потрібно уникнути
щоб вважати буквальними.

Не має значення, що робиться з уникнутим рядком, вставленим у врівноважений
оболонку регулярних виразів, додається, не має значення.

Зробіть заміну рядка за допомогою цього

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

про що ]?
Thomasleveil
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.