JavaScript 中是否有 RegExp.escape 函数?

Is there a RegExp.escape function in JavaScript?

提问人:Lance 提问时间:8/25/2010 最后编辑:Peter MortensenLance 更新时间:11/16/2023 访问量:192758

问:

我只想从任何可能的字符串中创建一个正则表达式。

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

有没有内置的方法?如果不是,人们使用什么?Ruby 具有 RegExp.escape。我不觉得我需要自己写,必须有一些标准的东西。

JavaScript 正则表达式

评论

20赞 Benjamin Gruenbaum 6/15/2015
只是想向你们更新RegExp.escape目前正在开发中,非常欢迎任何认为他们有宝贵意见的人做出贡献。Core-JS 和其他 polyfills 提供了它。
8赞 try-catch-finally 7/19/2017
根据最近更新的这个答案,这个提议被拒绝了:见问题
1赞 Drewry Pope 9/13/2020
是的,我相信@BenjaminGruenbaum可能是提出这个提议的人。我试图将代码示例和 es-shim npm 模块放入此处的堆栈溢出答案中:[ stackoverflow.com/a/63838890/5979634 ] 因为不幸的是,该提案最终被拒绝了。希望他们改变主意,或者有人在我退休之前实施“模板标签”。
1赞 urish 9/29/2023
上述提案刚刚进入第二阶段
0赞 ruX 12/2/2023
2023 年即将结束,但大多数流行的以字符串为中心的语言都没有内置正则表达式转义。这永远不会停止逗我。

答:

790赞 bobince 8/25/2010 #1

另一个答案中链接的功能是不够的。它无法转义 或 (字符串的开头和结尾),或者 ,在字符组中用于范围。^$-

使用此功能:

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

虽然乍一看似乎没有必要,但转义(以及 )使该函数适合于将要插入到字符类以及正则表达式主体中的转义字符。-^

转义使该函数适用于转义字符,以便在 JavaScript 正则表达式文本中使用,以便以后计算。/

由于逃避它们中的任何一个都没有缺点,因此逃避以涵盖更广泛的用例是有意义的。

是的,这是一个令人失望的失败,因为这不是标准 JavaScript 的一部分。

评论

23赞 thorn0 2/15/2013
其实,我们根本不需要逃跑/
41赞 bobince 10/3/2013
@Paul: Perl (), Python , PHP , Ruby ...quotemeta\Qre.escapepreg_quoteRegexp.quote
19赞 styfle 10/18/2013
如果你打算在循环中使用此函数,最好将 RegExp 对象设为自己的变量,然后你的函数是 这样你只实例化 RegExp 一次。var e = /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g;return s.replace(e, '\\$&');
38赞 bobince 9/16/2017
博明斯不在乎埃斯林特的意见
11赞 bobince 10/13/2017
但也许你想转义字符,把它们放在一个字符范围内。IMO 最好是无害地过度逃逸,而不是逃逸不足,并在利基情况下造成问题。FWIW 个人,我宁愿在这里明确地看到角色;我们不是在玩代码高尔夫。
22赞 Pierluc SS 10/31/2012 #2

在jQuery UI的自动完成小部件(版本1.9.1)中,他们使用略有不同的正则表达式(第6753行),这是正则表达式与bobince方法的结合。

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

评论

4赞 Martin Ender 7/8/2013
唯一的区别是它们转义(这不是元字符)和空格,它们仅在自由间距模式下才重要(JavaScript 不支持)。然而,他们确实做对了,没有逃脱正斜线。,#
20赞 Scott Stafford 8/20/2013
如果要重用jquery UI的实现,而不是将代码粘贴到本地,请使用.$.ui.autocomplete.escapeRegex(myString)
3赞 Ted Pennings 11/1/2015
Lodash 也有这个,_。escapeRegExp 和 npmjs.com/package/lodash.escaperegexp
0赞 Peter Krauss 3/7/2017
v1.12 一样,好!
5赞 kzh 9/5/2013 #3

这是一个较短的版本。

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

这包括 、 、 和 的非元字符,但 JavaScript RegExp 规范允许这样做。%&',

评论

3赞 nhahtdh 11/27/2014
我不会使用这个“较短”的版本,因为字符范围隐藏了字符列表,这使得乍一看更难验证正确性。
0赞 kzh 11/27/2014
@nhahtdh我可能也不会,但它发布在这里供参考。
0赞 Dan Dascalescu 11/28/2014
@kzh:发布“供参考”比发布理解更有帮助。难道你不同意我的答案更清楚吗?
0赞 Qwertiy 9/23/2017
至少,错过了。和。或不? 很奇怪。我不记得那里有什么。.()[-^
0赞 kzh 9/23/2017
这些都在指定的范围内。
48赞 quietmint 5/14/2014 #4

Mozilla Developer Network 的正则表达式指南提供了以下转义函数:

function escapeRegExp(string) {
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
0赞 Dan Dascalescu 8/2/2014 #5

其他答案中的函数对于转义整个正则表达式来说是大材小用的(它们对于转义正则表达式的一部分可能很有用,这些正则表达式稍后将连接成更大的正则表达式)。

如果你转义了一个完整的正则表达式并完成了它,那么你只需要引用独立的元字符(、、、、)、或开始一些东西(、、)。.?+*^$|\([{

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

是的,令人失望的是,JavaScript 没有内置这样的功能。

评论

1赞 nhahtdh 11/27/2014
假设您对用户输入进行转义,并将其插入: + input + 。您的方法将给出无法编译的结果字符串。请注意,这是一个非常合理的插入,而不是像 + input + 这样的疯入(在这种情况下,程序员可以被责怪做了一些愚蠢的事情)(text)next(?:)(?:\(text)next)re\re
1赞 Dan Dascalescu 11/28/2014
@nhahtdh:我的回答特别提到转义整个正则表达式并“完成”它们,而不是正则表达式的一部分(或未来部分)。请撤消反对票?
1赞 nhahtdh 11/28/2014
很少会出现转义整个表达式的情况 - 有字符串操作,如果您想使用文字字符串,与正则表达式相比,它要快得多。
2赞 nhahtdh 11/28/2014
请解决有关关闭的部分)
1赞 Qwertiy 9/23/2017
避免闭合大括号也是正确的,即使某些方言允许它们。我记得,这是一个扩展,而不是规则。
193赞 gustavohenke 4/17/2015 #6

对于使用 Lodash 的任何人,从 v3.0.0 开始,内置了 _.escapeRegExp 函数:

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

而且,如果您不想需要完整的 Lodash 库,您可能只需要该功能

评论

8赞 Ted Pennings 11/1/2015
甚至还有一个 npm 包就是这样!npmjs.com/package/lodash.escaperegexp
2赞 Rob Evans 8/31/2017
这会导入大量代码,而这些代码实际上不需要用于如此简单的事情。使用 bobince 的答案...对我有用,它比 Lodash 版本加载的字节数少得多!
12赞 gustavohenke 8/31/2017
@RobEvans我的答案以“对于任何使用 lodash 的人”开头,我什至提到您只需要该功能。escapeRegExp
3赞 Rob Evans 9/1/2017
@gustavohenke 对不起,我应该稍微清楚一点,我把链接到的模块包含在你的“只是那个功能”中,这就是我所评论的。如果你看一下,它应该是一个有效的单个函数,其中包含一个正则表达式,这是相当多的代码。如果您已经在使用 lodash,那么使用它是有意义的,但否则使用另一个答案。对不起,评论不清楚。
2赞 Federico Fissore 5/31/2018
@maddob我看不到你提到的 \x3:我的转义字符串看起来不错,正如我所期望的那样
55赞 Pi Marillion 6/16/2015 #7

这里的大多数表达式都解决了单个特定用例。

没关系,但我更喜欢“总是有效”的方法。

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 编译错误,而不是静默编译错误。
  • #,并且不需要在 JavaScript 中转义,但在许多其他风格中可以转义。它们在这里被转义,以防正则表达式稍后被传递给另一个程序。\s

如果您还需要使正则表达式面向未来,以防止 JavaScript 正则表达式引擎功能的潜在添加,我建议使用更偏执的:

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

此函数对每个字符进行转义,但那些明确保证不会用于将来正则表达式风格的语法的字符除外。


对于真正热衷于卫生的人,请考虑以下边缘情况:

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

这在 JavaScript 中应该可以很好地编译,但在其他一些风格中则不行。如果打算传递到另一种风格,则应独立检查 的 null 大小写,如下所示:s === ''

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

评论

1赞 Dan Dascalescu 7/4/2017
不需要在字符类中转义。/[...]
1赞 Qwertiy 9/22/2017
其中大多数不需要逃脱。“在字符类中创建字符范围” - 您永远不会位于字符串内的字符类中。“在自由间距模式下指定注释,在自由间距模式下忽略” - 在 javascript 中不受支持。“在枚举说明符中分隔值” - 您永远不会在字符串内的列数说明符中。此外,您不能在命名规范中编写任意文本。“开始或结束表达”——无需逃避。Eval 不是这种情况,因为它需要更多的逃逸。[将在下一条评论中继续]
0赞 Qwertiy 9/22/2017
“完成特殊的组类型,以及Perl样式字符类的一部分” - 似乎在javascript中不可用。“否定零宽度组,零宽度组规范的一部分” - 字符串内部永远不会有组。
1赞 Pi Marillion 9/23/2017
@Qwertiy 这些额外转义的原因是为了消除在某些用例中可能导致问题的边缘情况。例如,此函数的用户可能希望将转义的正则表达式字符串作为组的一部分插入到另一个正则表达式中,甚至用于 Javascript 以外的其他语言。该函数不会做出诸如“我永远不会成为字符类的一部分”之类的假设,因为它是通用的。有关更多 YAGNI 方法,请参阅此处的任何其他答案。
0赞 madprops 10/29/2017
非常好。为什么 _ 没有逃脱呢?是什么确保它以后可能不会成为正则表达式语法?
17赞 user663031 6/16/2015 #8

在 https://github.com/benjamingr/RexExp.escape/ 有一个 RegExp.escape 的 ES7 提案,在 https://github.com/ljharb/regexp.escape 有一个 polyfill。

评论

14赞 John 4/29/2017
看起来这没有进入 ES7。看起来它也被拒绝了,转而寻找模板标签
1赞 Drewry Pope 9/8/2020
@John是的,这看起来像是这种情况,此时整个概念已经被放弃了至少 5 年。我在这里添加了一个示例,因为它可能已经实现,而 TC39 仍然没有实现他们基于“标签”的解决方案。这似乎更符合您的期望,尽管我也可以将其视为 String.prototype 方法。在某些时候,他们应该重新考虑并实现这一点,即使他们绕过参数化正则表达式。不过,大多数其他语言都实现了转义,即使它们具有参数化查询,所以我们会看到。
0赞 Drewry Pope 9/13/2020
我根据这个提议添加了代码示例。感谢您添加这个答案,使我提出该提案。我试图编辑这个答案以添加确切的例子,但这被模组拒绝了。以下是代码示例的答案:[ stackoverflow.com/a/63838890/5979634 ]
4赞 Ravi Gadhia 4/29/2016 #9
escapeRegExp = function(str) {
  if (str == null) return '';
  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
};
15赞 filip 11/12/2016 #10

没有什么可以阻止你转义每个非字母数字字符:

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

在这样做时,你会失去一定程度的可读性,但你赢得了大量的简单性(和安全性)。re.toString()

根据 ECMA-262,一方面,正则表达式“语法字符”始终是非字母数字的,因此结果是安全的,并且特殊的转义序列 (, , ) 始终是字母数字的,因此不会产生错误的控制转义。\d\w\n

评论

1赞 Tomas Langkaas 8/10/2017
简单有效。我比公认的答案更喜欢这个。对于(真的)旧浏览器,将以相同的方式工作。.replace(/[^\w]/g, '\\$&')
8赞 Alexey Lebedev 2/2/2018
这在 Unicode 模式下失败。例如,引发异常,因为分别匹配代理项对的每个代码单元,从而导致无效的转义码。new RegExp('🍎'.replace(/(?=\W)/g, '\\'), 'u')\W
2赞 pmiguelpinto90 3/21/2018
另类:.replace(/\W/g, "\\$&");
1赞 johny why 4/27/2020
@AlexeyLebedev 答案是否已修复以处理Unicode模式?或者其他地方有没有解决方案,同时保持这种简单性?
4赞 Antoine Dusséaux 7/6/2017 #11

XRegExp 有一个转义函数:

XRegExp.escape('Escaped? <.>'); // -> 'Escaped\?\ <\.>'

更多信息:http://xregexp.com/api/#escape

3赞 bashaus 8/1/2017 #12

与其只转义会导致正则表达式出现问题的字符(例如:黑名单),不如考虑使用白名单。这样,除非匹配,否则每个字符都被视为有污点。

对于此示例,假设以下表达式:

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

这会将字母、数字和空格列入白名单:

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

返回:

"be \|\| \! be"

这可能会逃脱不需要转义的角色,但这并不妨碍你的表达(也许会有一些轻微的时间惩罚 - 但为了安全起见,这是值得的)。

评论

0赞 johny why 4/27/2020
他的回答和@filip的回答有什么不同吗?stackoverflow.com/a/40562456/209942
8赞 soheilpro 8/18/2019 #13

另一种(更安全)的方法是使用 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');

评论

0赞 Wendell Pereira 7/28/2020
更安全!并准备好未来的正则表达式实现!
1赞 user557597 9/18/2019 #14

只有 12 个元角色需要转义,而且永远都会有 被视为文字。

对转义字符串执行什么操作、插入到平衡的正则表达式包装器中或附加这些字符串并不重要。这不重要。

使用此方法执行字符串替换

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

评论

1赞 Thomasleveil 10/6/2019
怎么样?]
0赞 AnrDaemon 3/31/2023
如果您使用理智的解析器,则不需要转义。
0赞 Terje Rosenlund 8/19/2023
如果开口 [(或 {)被转义,我们不需要转义关闭 ](或 })。不是一样吗?
0赞 Terje Rosenlund 8/19/2023
我的评论应该是为什么对 ) 不一样?
12赞 Drewry Pope 9/11/2020 #15

在 https://github.com/benjamingr/RexExp.escape/ 上有一个 RegExp.escape 的 ES7 提案,在 https://github.com/ljharb/regexp.escape 上有一个 polyfill。

基于被拒绝的 ES 提案的示例包括检查该属性是否已经存在,以防 TC39 撤回其决定。


法典:

if (!Object.prototype.hasOwnProperty.call(RegExp, 'escape')) {
  RegExp.escape = function(string) {
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping
    // https://github.com/benjamingr/RegExp.escape/issues/37
    return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
  };
}

代码缩小:

Object.prototype.hasOwnProperty.call(RegExp,"escape")||(RegExp.escape=function(e){return e.replace(/[.*+\-?^${}()|[\]\\]/g,"\\$&")});

// ...
var assert = require('assert');
 
var str = 'hello. how are you?';
var regex = new RegExp(RegExp.escape(str), 'g');
assert.equal(String(regex), '/hello\. how are you\?/g');

还有一个模块:https://www.npmjs.com/package/regexp.escapenpm


可以安装它并按如下方式使用它:


npm install regexp.escape

yarn add regexp.escape

var escape = require('regexp.escape');
var assert = require('assert');
 
var str = 'hello. how are you?';
var regex = new RegExp(escape(str), 'g');
assert.equal(String(regex), '/hello\. how are you\?/g');

在 GitHub & & NPM 页面中,还描述了如何使用此选项的 shim/polyfill。该逻辑基于 ,其中实现包含上面使用的正则表达式。return RegExp.escape || implementation;


NPM 模块是一个额外的依赖项,但它也使外部贡献者更容易识别添加到代码中的逻辑部分。̄\(ツ)

评论

1赞 Drewry Pope 9/13/2020
这个答案的开头与 [ stackoverflow.com/a/30852428/5979634 ] 相同,我曾希望编辑他们的答案以包含这些信息,但更简单的版本被认为与原始答案相差太大。我想我在网站中提供了实际的代码示例,但我不打算争论。取而代之的是,我提供了一个新的、扩展的答案,因为它与另一个这样的答案太不同了。
1赞 dx_over_dt 10/28/2021 #16

我借用了上面 bobince 的答案,并创建了一个标记模板函数,用于创建一个 其中部分值被转义,部分不被转义。RegExp

正则表达式转义.js

RegExp.escape = text => text.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');

RegExp.escaped = flags =>
  function (regexStrings, ...escaped) {
    const source = regexStrings
      .map((s, i) =>
        // escaped[i] will be undefined for the last value of s
        escaped[i] === undefined
          ? s
          : s + RegExp.escape(escaped[i].toString())
      )
      .join('');
    return new RegExp(source, flags);
  };
  
function capitalizeFirstUserInputCaseInsensitiveMatch(text, userInput) {
  const [, before, match, after ] =
    RegExp.escaped('i')`^((?:(?!${userInput}).)*)(${userInput})?(.*)$`.exec(text);

  return `${before}${match.toUpperCase()}${after}`;
}

const text = 'hello (world)';
const userInput = 'lo (wor';
console.log(capitalizeFirstUserInputCaseInsensitiveMatch(text, userInput));

对于我们的 TypeScript 粉丝...

global.d.ts(全球.d.ts)

interface RegExpConstructor {
  /** Escapes a string so that it can be used as a literal within a `RegExp`. */
  escape(text: string): string;

  /**
   * Returns a tagged template function that creates `RegExp` with its template values escaped.
   *
   * This can be useful when using a `RegExp` to search with user input.
   *
   * @param flags The flags to apply to the `RegExp`.
   *
   * @example
   *
   * function capitalizeFirstUserInputCaseInsensitiveMatch(text: string, userInput: string) {
   *   const [, before, match, after ] =
   *     RegExp.escaped('i')`^((?:(?!${userInput}).)*)(${userInput})?(.*)$`.exec(text);
   *
   *   return `${before}${match.toUpperCase()}${after}`;
   * }
   */
  escaped(flags?: string): (regexStrings: TemplateStringsArray, ...escapedVals: Array<string | number>) => RegExp;
}

评论

0赞 Beni Cherniavsky-Paskin 1/12/2023
整洁!简单的escape()函数与用于标准化的标记模板的优缺点已经讨论了多年:github.com/tc39/proposal-regex-escaping/issues/45 - 它链接到几个更多的标记实现。
0赞 Ahsan Khan 8/24/2022 #17

这是永久的解决方案。

function regExpEscapeFuture(literal_string) {
     return literal_string.replace(/[^A-Za-z0-9_]/g, '\\$&');
}
0赞 Keego 12/1/2022 #18

刚刚发布了一个基于 RegExp.escape 填充码的正则表达式转义要点,而该填充码又基于被拒绝的 RegExp.escape 提案看起来与公认的答案大致相同,只是它没有转义字符,根据我的手动测试,这似乎实际上没问题。-

撰写本文时的当前要点:

const syntaxChars = /[\^$\\.*+?()[\]{}|]/g

/**
 * Escapes all special special regex characters in a given string
 * so that it can be passed to `new RegExp(escaped, ...)` to match all given
 * characters literally.
 *
 * inspired by https://github.com/es-shims/regexp.escape/blob/master/implementation.js
 *
 * @param {string} s
 */
export function escape(s) {
  return s.replace(syntaxChars, '\\$&')
}
0赞 john nowlin 11/16/2023 #19

这是一个非基于正则表达式的 replaceAll 版本,其运行速度比类似的基于正则表达式的版本快 40%。

使用 string.includes 函数和三元添加转义反斜杠而不是正则表达式。

const escapeRegex = s => s.replaceAll("", a => "\\/-^$*+?.|()[]{}".includes(a) ? "\\" + a : a)