Czy w Javascript jest dostępna funkcja RegExp.escape?


442

Chcę tylko utworzyć wyrażenie regularne z dowolnego możliwego ciągu.

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

Czy istnieje do tego wbudowana metoda? Jeśli nie, z czego korzystają ludzie? Ruby ma RegExp.escape. Nie wydaje mi się, żebym musiał pisać własne, musi być coś standardowego. Dzięki!


15
Chciałem tylko zaktualizować ci świetnych ludzi, nad którymi RegExp.escapeobecnie pracujemy, a każdy, kto uważa, że ​​mają cenny wkład, jest bardzo mile widziany. Core-js i inne wypełniacze to oferują.
Benjamin Gruenbaum

Odpowiedzi:


573

Funkcja powiązana powyżej jest niewystarczająca. Nie można uciec ^lub $(początek i koniec łańcucha), lub -, który w grupie znaków jest używany dla zakresów.

Użyj tej funkcji:

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

Choć na pierwszy rzut oka może się to wydawać niepotrzebne, funkcja zmiany znaczenia -(a także ^) sprawia, że ​​funkcja ta jest odpowiednia do wstawiania znaków do klasy znaków, a także treści wyrażenia regularnego.

/Funkcja Escaping sprawia, że ​​funkcja jest odpowiednia do zmiany znaczenia znaków i może być używana w dosłownym wyrażeniu regularnym JS do późniejszej ewaluacji.

Ponieważ nie ma żadnej wady ucieczki od któregoś z nich, sensowne jest ucieczka, aby objąć szersze przypadki użycia.

I tak, rozczarowujące jest to, że nie jest to część standardowego JavaScript.


16
Właściwie nie musimy uciekać /w ogóle
Thorn

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

13
Jeśli zamierzasz używać tej funkcji w pętli, prawdopodobnie najlepiej jest uczynić obiekt RegExp jego własną zmienną, var e = /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g;a następnie twoją funkcją jest W return s.replace(e, '\\$&');ten sposób tworzysz RegExp tylko raz.
styfle

15
Obowiązują tutaj standardowe argumenty przeciwko rozszerzaniu wbudowanych obiektów, nie? Co się stanie, jeśli przyszła wersja ECMAScript zapewnia, RegExp.escapektórej implementacja różni się od twojej? Czy nie byłoby lepiej, gdyby ta funkcja nie była do niczego dołączona?
Mark Amery

15
troski bobince nie dla eslint OPINIĄ
bobince

114

Dla każdego, kto za pomocą lodash, ponieważ v3.0.0 _.escapeRegExp funkcją jest wbudowany w:

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

W przypadku, gdy nie chcesz wymagać pełnej biblioteki lodash, możesz potrzebować tylko tej funkcji !


6
jest nawet pakiet npm tego! npmjs.com/package/lodash.escaperegexp
Ted Pennings

1
Spowoduje to zaimportowanie dużej ilości kodu, który tak naprawdę nie musi być dostępny dla tak prostej rzeczy. Użyj odpowiedzi Bobina ... działa dla mnie i jego ładowanie jest o wiele mniej bajtów niż wersja lodash!
Rob Evans

6
@RobEvans moja odpowiedź zaczyna się od „Dla każdego, kto używa lodash” , a nawet wspominam, że możesz wymagać tylko tej escapeRegExpfunkcji.
gustavohenke

2
@gustavohenke Przykro mi, ale powinienem był być nieco bardziej przejrzysty. Włączyłem moduł powiązany z Twoją „właśnie tą funkcją” i właśnie to komentowałem. Jeśli spojrzysz, to całkiem sporo kodu, który powinien skutecznie być pojedynczą funkcją z jednym wyrażeniem regularnym. Zgadzam się, jeśli już używasz lodash, wtedy warto go użyć, ale w przeciwnym razie skorzystaj z innej odpowiedzi. Przepraszamy za niejasny komentarz.
Rob Evans

2
@maddob Nie widzę, że \ x3 wspomniałeś: moje uciekające sznurki wyglądają dobrze, właśnie tego oczekuję
Federico Fissore

43

Większość wyrażeń tutaj rozwiązuje pojedyncze przypadki użycia.

W porządku, ale wolę podejście „zawsze działa”.

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

Spowoduje to „całkowite uniknięcie” literału dla dowolnego z poniższych zastosowań w wyrażeniach regularnych:

  • Wstawienie w wyrażeniu regularnym. Na przykładnew RegExp(regExpEscape(str))
  • Wstawienie do klasy postaci. Na przykładnew RegExp('[' + regExpEscape(str) + ']')
  • Wstawienie do specyfikatora liczby całkowitej. Na przykładnew RegExp('x{1,' + regExpEscape(str) + '}')
  • Wykonanie w silnikach wyrażeń regularnych innych niż JavaScript.

Objęte znaki specjalne:

  • -: Tworzy zakres znaków w klasie znaków.
  • [/ ]: Zaczyna / kończy klasę znaków.
  • {/ }: Uruchamia / kończy specyfikator numeracji.
  • (/ ): Zaczyna / kończy grupę.
  • */ +/ ?: Określa typ powtarzania.
  • .: Pasuje do dowolnej postaci.
  • \: Ucieka znaki i uruchamia byty.
  • ^: Określa początek pasującej strefy i neguje dopasowanie w klasie znaków.
  • $: Określa koniec pasującej strefy.
  • |: Określa naprzemienność.
  • #: Określa komentarz w trybie wolnych odstępów.
  • \s: Ignorowane w trybie swobodnych odstępów.
  • ,: Oddziela wartości w specyfikatorze numeracji.
  • /: Rozpoczyna lub kończy wyrażenie.
  • :: Uzupełnia specjalne typy grup i część klas postaci w stylu Perla.
  • !: Neguje grupę o zerowej szerokości.
  • </ =: Część specyfikacji grupy o zerowej szerokości.

Uwagi:

  • /nie jest absolutnie konieczne w żadnym smaku wyrażenia regularnego. Jednakże, chroni w przypadku gdy ktoś (Dreszcz) robi eval("/" + pattern + "/");.
  • , zapewnia, że ​​jeśli ciąg ma być liczbą całkowitą w specyfikatorze liczbowym, poprawnie spowoduje błąd kompilacji RegExp zamiast kompilacji po cichu niepoprawnej.
  • #i \snie trzeba zmieniać znaczenia w JavaScript, ale w wielu innych odmianach. Są one tutaj usuwane na wypadek, gdyby wyrażenie regularne zostało później przekazane do innego programu.

Jeśli potrzebujesz również w przyszłości zabezpieczyć wyrażenie regularne przed potencjalnymi dodatkami do możliwości silnika regex JavaScript, zalecam użycie bardziej paranoicznej:

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

Ta funkcja unika każdego znaku z wyjątkiem tych, które wyraźnie gwarantują, że nie zostaną użyte w składni w przyszłych smakach wyrażeń regularnych.


Jeśli naprawdę zależy Ci na higienie, rozważ ten przypadek:

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

To powinno dobrze skompilować się w JavaScript, ale nie będzie w innych smakach. Jeśli zamierzasz przejść do innego smaku, zerowy przypadek s === ''powinien być niezależnie sprawdzony, tak jak:

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

1
/Nie musi być uciekł w [...]klasy postaci.
Dan Dascalescu

1
Większość z nich nie wymaga ucieczki. „Tworzy zakres znaków w klasie znaków” - nigdy nie należysz do klasy znaków w ciągu. „Określa komentarz w trybie wolnych odstępów, ignorowany w trybie swobodnych odstępów” - nieobsługiwany w javascript. „Oddziela wartości w specyfikatorze numeracji” - nigdy nie znajdujesz się w specyfikatorze numeracji w ciągu. Nie można także pisać dowolnego tekstu w specyfikacji nazewnictwa. „Zaczyna lub kończy wyrażenie” - nie trzeba uciekać. Eval nie jest przypadkiem, ponieważ wymagałoby znacznie więcej ucieczki. [będzie kontynuowany w następnym komentarzu]
Qwertiy

„Uzupełnia specjalne typy grup i część klas postaci w stylu Perla” - wydaje się niedostępny w javascript. „Neguje grupę o zerowej szerokości, część specyfikacji grupy o zerowej szerokości” - nigdy nie ma grup wewnątrz ciągu.
Qwertiy

@Qwertiy Powodem tych dodatkowych znaków ucieczki jest wyeliminowanie przypadków krawędzi, które mogą powodować problemy w niektórych przypadkach użycia. Na przykład użytkownik tej funkcji może chcieć wstawić ciąg znaków ucieczki do innego wyrażenia regularnego jako część grupy, a nawet do użycia w innym języku niż Javascript. Funkcja nie przyjmuje założeń typu „Nigdy nie będę częścią klasy postaci”, ponieważ ma być ogólna . Więcej informacji na temat YAGNI można znaleźć w innych odpowiedziach tutaj.
Pi Marillion,

Bardzo dobre. Dlaczego nie uciec? Co gwarantuje, że prawdopodobnie nie stanie się później składnią wyrażenia regularnego?
madprops


21

W widżecie autouzupełniania jQueryUI (wersja 1.9.1) używają nieco innego wyrażenia regularnego (wiersz 6753), oto wyrażenie regularne połączone z podejściem @bobince.

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

4
Jedyną różnicą jest to, że uciekają ,(co nie jest metaznakiem), i #białe znaki, które mają znaczenie tylko w trybie swobodnych odstępów (który nie jest obsługiwany przez JavaScript). Jednak mają rację, aby nie uciec przed ukośnikiem.
Martin Ender

18
Jeśli chcesz ponownie użyć implementacji interfejsu jquery zamiast wklejać kod lokalnie, idź z $.ui.autocomplete.escapeRegex(myString).
Scott Stafford,

2
lodash też to ma, _. escapeRegExp i npmjs.com/package/lodash.escaperegexp
Ted Pennings

v1.12 to samo, ok!
Peter Krauss,

13

Nic nie powinno powstrzymywać Cię przed ucieczką od każdego niealfanumerycznego znaku:

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

Tracisz przy tym pewien stopień czytelności, re.toString()ale zyskujesz dużą prostotę (i bezpieczeństwo).

Zgodnie z ECMA-262, z jednej strony, regularne wyrażenie „znaków składniowe” są zawsze niealfanumeryczne tak, że wynik jest bezpieczna, a szczególne sekwencje ( \d, \w, \n) są zawsze alfanumeryczny takie, że żadne fałszywe ucieka kontroli będzie produkowany .


Prosty i skuteczny. Lubię to znacznie lepiej niż zaakceptowana odpowiedź. W przypadku (naprawdę) starych przeglądarek .replace(/[^\w]/g, '\\$&')działałoby to w ten sam sposób.
Tomas Langkaas,

6
Nie udaje się to w trybie Unicode. Na przykład new RegExp('🍎'.replace(/(?=\W)/g, '\\'), 'u')zgłasza wyjątek, ponieważ \Wdopasowuje każdą jednostkę kodu pary zastępczej osobno, co powoduje nieprawidłowe kody ucieczki.
Aleksiej Lebiediew

1
alternatywnie:.replace(/\W/g, "\\$&");
Miguel Pynto

@AlexeyLebedev Czy odpowiedź została naprawiona do obsługi trybu Unicode? Czy istnieje rozwiązanie gdzie indziej, zachowując tę ​​prostotę?
John, dlaczego


6

To jest krótsza wersja.

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

Obejmuje to non-meta znaków %, &, 'oraz ,, ale specyfikacja JavaScript RegExp pozwala na to.


2
Nie użyłbym tej „krótszej” wersji, ponieważ zakresy znaków ukrywają listę znaków, co utrudnia sprawdzenie poprawności na pierwszy rzut oka.
nhahtdh

@ nhahtdh Prawdopodobnie też bym tego nie zrobił, ale zamieszczono go tutaj w celach informacyjnych.
kzh

@kzh: publikowanie „w celach informacyjnych” pomaga mniej niż publikowanie w celu zrozumienia. Czy nie zgodziłbyś się, że moja odpowiedź jest jaśniejsza?
Dan Dascalescu

Przynajmniej .brakuje. I ()… Albo nie? [-^jest dziwny. Nie pamiętam co tam jest.
Qwertiy

Są w określonym zakresie.
kzh


3

Zamiast tylko uciekających znaków, które spowodują problemy w wyrażeniu regularnym (np. Czarna lista), dlaczego nie rozważyć użycia białej listy. W ten sposób każda postać jest uważana za skażoną, chyba że pasuje.

W tym przykładzie przyjmij następujące wyrażenie:

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

Ta biała lista zawiera litery, cyfry i spacje:

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

Zwroty:

"be \|\| \! be"

Może to oznaczać ucieczkę postaci, które nie wymagają ucieczki, ale nie przeszkadza to w wyrażeniu (być może niewielkie kary czasowe - ale warto dla bezpieczeństwa).


Czy to różni się od odpowiedzi @ filip? stackoverflow.com/a/40562456/209942
John, dlaczego

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

1

Funkcje w pozostałych odpowiedziach są nadmiernie zblokowane, aby uciec przed całymi wyrażeniami regularnymi (mogą być przydatne do ucieczki części wyrażeń regularnych, które później zostaną połączone w większe wyrażenia regularne).

Jeśli ucieczka całego wyrażenia regularnego i są z nim zrobić, cytując metaznaków które są albo samodzielnym ( ., ?, +, *, ^, $, |, \) lub uruchomić coś ( (, [, {) to wszystko, czego potrzebujesz:

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

I tak, to rozczarowujące, że JavaScript nie ma takiej funkcji jak ta wbudowana.


Powiedzmy, że uciekasz od danych wejściowych użytkownika (text)nexti wstawiasz je: (?:+ input + ). Twoja metoda da wynikowy ciąg, (?:\(text)next)który się nie skompiluje. Zauważ, że jest to całkiem rozsądne wstawienie, a nie jakieś szalone, takie jak re\+ input + re(w tym przypadku programistę można obwiniać za zrobienie czegoś głupiego)
nhahtdh

1
@nhahtdh: moja odpowiedź konkretnie wspomniała o unikaniu całych wyrażeń regularnych i „wykonywaniu” ich, a nie o częściach (lub przyszłych częściach) wyrażeń regularnych. Uprzejmie cofnąć głosowanie?
Dan Dascalescu

Rzadko zdarza się, że unikasz całego wyrażenia - są operacje na łańcuchach, które są znacznie szybsze w porównaniu do wyrażeń regularnych, jeśli chcesz pracować z literałem.
nhahtdh

Nie wspominając o tym, że jest niepoprawny - \należy go uciec, ponieważ wyrażenie regularne pozostanie \wnienaruszone. Poza tym JavaScript wydaje się nie pozwalać na śledzenie ), przynajmniej w tym przypadku Firefox zgłasza błąd.
nhahtdh

1
Proszę odnieść się do części dotyczącej zamknięcia)
nhahtdh

1

Innym (znacznie bezpieczniejszym) podejściem jest ucieczka od wszystkich znaków (a nie tylko kilku specjalnych, które obecnie znamy) przy użyciu formatu unikodu \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}'

Pamiętaj, że musisz przekazać uflagę, aby ta metoda działała:

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

1

Dotychczas istniało i będzie 12 znaków meta, które należy uciec,
aby uznać je za dosłowne.

Nie ma znaczenia, co się dzieje z ciągiem znaków ucieczki, wstawionym do zbalansowanego
wyrażenia regularnego, dołączonym, nie ma znaczenia.

Wykonaj zamianę ciągu za pomocą tego

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

co ]?
Thomasleveil,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.