Существует ли функция RegExp.escape в Javascript?

443

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

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

Есть ли встроенный метод для этого? Если нет, что люди используют? Руби имеет RegExp.escape. Я не чувствую, что мне нужно писать свое, должно быть что-то стандартное. Спасибо!

Лэнс Поллард
источник
15
Просто хотел бы рассказать вам о замечательных людях, над которыми RegExp.escapeв настоящее время работаю, и любой, кто считает, что у них есть ценный вклад, может помочь. core-js и другие polyfills предлагают это.
Бенджамин Грюнбаум
5
Согласно последнему обновлению этого ответа, это предложение было отклонено: Смотрите вопрос
try-catch-finally

Ответы:

574

Связанная выше функция недостаточна. Не удается избежать ^или $(начало и конец строки), или -, который в группе символов используется для диапазонов.

Используйте эту функцию:

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

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

Экранирование /делает функцию подходящей для экранирования символов, которые будут использоваться в литерале регулярного выражения JS для последующего вычисления.

Так как нет побочных эффектов от побега любого из них, имеет смысл сбежать, чтобы охватить более широкие варианты использования.

И да, досадно, что это не является частью стандартного JavaScript.

bobince
источник
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 не заботится о мнении
Эслинт
115

Для тех , кто с помощью lodash, поскольку v3.0.0 _.escapeRegExp функция встроена в:

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

И, если вам не нужна полная библиотека lodash, вам может потребоваться только эта функция !

gustavohenke
источник
6
есть даже пакет npm только этого! npmjs.com/package/lodash.escaperegexp
Тед Пеннингс
1
Это импортирует множество кода, который действительно не должен быть там для такой простой вещи. Воспользуйтесь ответом Бобинса ... у меня работает, и его количество загружаемых байт намного меньше, чем у версии lodash!
Роб Эванс
6
@RobEvans мой ответ начинается с «Для тех, кто использует lodash» , и я даже упоминаю, что вам может потребоваться только эта escapeRegExpфункция.
густавохенке
2
@gustavohenke Извините, я должен был быть немного более ясным, я включил модуль, связанный с вашей «просто этой функцией», и это то, что я комментировал. Если вы посмотрите, то это довольно много кода для того, что должно быть единственной функцией с одним регулярным выражением. Согласитесь, если вы уже используете lodash, тогда имеет смысл использовать его, но в противном случае используйте другой ответ. Извините за неясный комментарий.
Роб Эванс
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, но делают во многих других вариантах. Они здесь экранированы на случай, если регулярное выражение будет позже передано другой программе.

Если вам также необходимо защитить регулярное выражение от потенциальных добавлений к возможностям механизма регулярных выражений 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. «Разделяет значения в спецификаторе нумерации» - вы никогда не входите в спецификатор нумерации внутри строки. Также вы не можете написать произвольный текст внутри спецификации именования. «Начинает или заканчивает выражение» - не нужно убегать. Eval - это не тот случай, так как он потребует гораздо большего ухода. [будет продолжено в следующем комментарии]
Qwertiy
«Завершает специальные типы групп и часть классов символов в стиле Perl» - кажется, недоступно в javascript. «Отрицание группы нулевой ширины, часть спецификаций группы нулевой ширины» - внутри строки никогда не бывает групп.
Qwertiy
@Qwertiy Причина этих дополнительных выходов заключается в устранении крайних случаев, которые могут вызвать проблемы в определенных случаях использования. Например, пользователь этой функции может захотеть вставить экранированную строку регулярного выражения в другое регулярное выражение как часть группы или даже для использования на другом языке, кроме Javascript. Функция не делает предположений типа «Я никогда не буду частью класса символов», потому что она должна быть общей . Для более подходящего подхода YAGNI, смотрите любые другие ответы здесь.
Пи
Отлично. Почему _ не удалось избежать? Что гарантирует, что он, вероятно, не станет синтаксисом регулярных выражений позже?
madprops
21

В виджете автозаполнения jQueryUI (версия 1.9.1) они используют немного другое регулярное выражение (строка 6753), вот регулярное выражение в сочетании с подходом @bobince.

RegExp.escape = function( value ) {
     return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
}
Pierluc SS
источник
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) всегда алфавитно - цифровой , такие , что никакие ложные ускользает управления не будет производиться ,

Filip
источник
Просто и эффективно. Мне это нравится намного лучше, чем принятый ответ. Для (действительно) старых браузеров, .replace(/[^\w]/g, '\\$&')будет работать так же.
Томас Лангкаас
6
Это не работает в режиме Unicode. Например, new RegExp('🍎'.replace(/(?=\W)/g, '\\'), 'u')выдает исключение, потому что \Wсопоставляет каждую единицу кода суррогатной пары отдельно, что приводит к недопустимым управляющим кодам.
Алексей Лебедев
1
альтернатива:.replace(/\W/g, "\\$&");
Мигель Пинто
@AlexeyLebedev Hes ответ был исправлен для обработки режима Unicode? Или в другом месте есть решение, которое поддерживает эту простоту?
Джонни, почему
6

Это более короткая версия.

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

Это включает в себя не-мета - символы %, &, ', и ,, но спецификация JavaScript RegExp позволяет.

КЖ
источник
2
Я бы не стал использовать эту «более короткую» версию, поскольку диапазоны символов скрывают список символов, что затрудняет проверку правильности на первый взгляд.
nhahtdh
@nhahtdh Я, вероятно, не хотел бы, но это размещено здесь для информации.
kzh
@kzh: публикация «для информации» помогает меньше, чем публикация для понимания. Вы не согласны с тем, что мой ответ яснее?
Дан Даскалеску
По крайней мере, .пропущено. И (). Или нет? [-^странно. Я не помню, что там.
Qwertiy
Те находятся в указанном диапазоне.
17
3

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

Для этого примера предположим следующее выражение:

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

Это белый список букв, цифр и пробелов:

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

Возвращает:

"be \|\| \! be"

Это может скрывать символы, от которых не нужно убегать, но это не мешает вашему выражению (возможно, некоторые незначительные штрафы за время - но это стоит того для безопасности).

bashaus
источник
Его это отличается от ответа @ Филип? stackoverflow.com/a/40562456/209942
Джонни, почему
3
escapeRegExp = function(str) {
  if (str == null) return '';
  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
};
Рави Гадхия
источник
1

Функции в других ответах излишни для экранирования целых регулярных выражений (они могут быть полезны для экранирования частей регулярных выражений, которые впоследствии будут объединены в большие регулярные выражения).

Если вы бежите все регулярное выражение и сделали с ним, ссылаясь на метасимволы , которые являются либо автономными ( ., ?, +, *, ^, $, |, \) или начать что - то ( (, [, {) есть все , что вам нужно:

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

И да, разочаровывает то, что в JavaScript нет такой функции, как эта.

Дан Дакалеску
источник
Допустим, вы избегаете ввода пользователя (text)nextи вставляете его в: (?:+ input + ). Ваш метод выдаст результирующую строку, (?:\(text)next)которая не компилируется. Обратите внимание, что это вполне разумная вставка, а не какая-то сумасшедшая, такая как re\+ input + re(в этом случае программиста можно обвинить в том, что он сделал что-то глупое)
nhahtdh
1
@nhahtdh: в моем ответе конкретно говорилось о том, что нужно избегать целых регулярных выражений и "завершаться" с ними, а не частями (или будущими частями) регулярных выражений Пожалуйста, отмените понижающий голос?
Дан Даскалеску
Редко бывает так, что вы избегаете всего выражения - есть строковые операции, которые намного быстрее по сравнению с регулярными выражениями, если вы хотите работать с литеральной строкой.
nhahtdh
Это не говоря о том, что это неверно - \следует избегать, так как ваше регулярное выражение останется \wнетронутым. Кроме того, JavaScript, похоже, не допускает трейлинг ), по крайней мере, именно для этого Firefox выдает ошибку.
nhahtdh
1
Пожалуйста, обратитесь к части о закрытии)
nhahtdh
1

Другой (гораздо более безопасный) подход состоит в том, чтобы экранировать все символы (а не только несколько специальных, которые мы знаем в настоящее время), используя 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}'

Обратите внимание, что вам нужно передать uфлаг, чтобы этот метод работал:

var expression = new RegExp(escapeRegExp(usersString), 'u');
soheilpro
источник
1

Только когда-либо было и будет 12 метасимволов, которые нужно экранировать,
чтобы считаться литералом.

Неважно, что делается с экранированной строкой, вставленной в сбалансированную
оболочку регулярных выражений, с добавлением, не имеет значения.

Заменить строку, используя это

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

источник
о чем ]?
Thomasleveil