String.replace Wszystkie pojedyncze ukośniki odwrotne z podwójnymi ukośnikami odwrotnymi

122

Próbuję przekonwertować String \something\go na String \\something\\używanie replaceAll, ale wciąż otrzymuję wszelkiego rodzaju błędy. Myślałem, że to rozwiązanie:

theString.replaceAll("\\", "\\\\");

Ale to daje poniższy wyjątek:

java.util.regex.PatternSyntaxException: Unexpected internal error near index 1
Frank Groeneveld
źródło

Odpowiedzi:

205

String#replaceAll()Interpretuje argumentu jako wyrażenie regularne . \Jest znakiem ucieczki w obu String i regex. W przypadku wyrażenia regularnego musisz uciec podwójnie:

string.replaceAll("\\\\", "\\\\\\\\");

Ale niekoniecznie potrzebujesz do tego wyrażenia regularnego, po prostu dlatego, że chcesz dokładnie zastąpić znak po znaku i nie potrzebujesz tutaj wzorców. String#replace()Powinno więc wystarczyć:

string.replace("\\", "\\\\");

Aktualizacja : zgodnie z komentarzami, wydaje się, że chcesz użyć ciągu w kontekście JavaScript. Może lepiej użyć StringEscapeUtils#escapeEcmaScript()zamiast tego, aby zakryć więcej postaci.

BalusC
źródło
W rzeczywistości jest używany w JavaScript AST, który powinien zostać przekonwertowany z powrotem na źródło. Twoje rozwiązanie działa. Dzięki!
Frank Groeneveld
2
Jeśli String#replaceAll()mimo wszystko chcesz użyć , możesz zacytować ciąg zastępujący za pomocą Matcher # quoteReplacement () :theString.replaceAll("\\", Matcher.quoteReplacement("\\\\"));
phse
Matcher.quoteReplacement (...) to dobry sposób! Zobacz odpowiedź Pshemo!
Hartmut P.
14

Aby uniknąć tego rodzaju problemów, możesz użyć replace(który pobiera zwykły ciąg) zamiast replaceAll(który przyjmuje wyrażenie regularne). Nadal będziesz musiał unikać odwrotnych ukośników, ale nie w sposób dziki wymagany w wyrażeniach regularnych.

Fabian Steeg
źródło
10

TLDR: użyj theString = theString.replace("\\", "\\\\");zamiast tego.


Problem

replaceAll(target, replacement)używa składni wyrażeń regularnych (regex) dla targeti częściowo dla replacement.

Problem polega na tym, że \jest to znak specjalny w wyrażeniu regularnym (może być używany tak jak \ddo reprezentacji cyfry) i w literale String (może być używany jak "\n"do reprezentowania separatora linii lub \"do ucieczki przed symbolem podwójnego cudzysłowu, który normalnie oznaczałby koniec literału ciągu).

W obu tych przypadkach, aby stworzyć \symbol, możemy przed nim uciec (uczynić go dosłownym zamiast znaku specjalnego), umieszczając \przed nim dodatkowe (tak jak "w przypadku literałów ciągu przez \").

Tak więc targetregex reprezentujący \symbol będzie musiał się trzymać \\, a literał łańcuchowy reprezentujący taki tekst będzie musiał wyglądać "\\\\".

Więc uciekliśmy \dwa razy:

  • raz w wyrażeniu regularnym \\
  • raz w literale String "\\\\"(każdy \jest reprezentowany jako "\\").

W przypadku replacement \jest też tam wyjątkowy. Pozwala nam uciec przed innym znakiem specjalnym, $który za pomocą $xnotacji pozwala nam użyć części danych dopasowanych przez wyrażenie regularne i przechowywanych przez grupę przechwytywania indeksowaną tak x, jak "012".replaceAll("(\\d)", "$1$1")będzie pasowała do każdej cyfry, umieści ją w grupie przechwytywania 1 i $1$1zastąpi ją dwoma kopiami (zduplikuje to), w wyniku czego "001122".

Więc znowu, aby replacementreprezentować \dosłownie, musimy uciec od tego dodatkowym, \co oznacza, że:

  • zamiennik musi zawierać dwa znaki ukośnika odwrotnego \\
  • i literał String, który reprezentuje \\wygląd"\\\\"

ALE ponieważ chcemy replacementtrzymać dwa ukośniki odwrotne, których będziemy potrzebować "\\\\\\\\"(każdy \reprezentowany przez jeden "\\\\").

Więc wersja z replaceAllmoże wyglądać

replaceAll("\\\\", "\\\\\\\\");

Łatwiejszy sposób

Aby ułatwić życie, Java udostępnia narzędzia do automatycznego wprowadzania tekstu do targeti replacementczęści. Więc teraz możemy skupić się tylko na łańcuchach i zapomnieć o składni regex:

replaceAll(Pattern.quote(target), Matcher.quoteReplacement(replacement))

co w naszym przypadku może wyglądać

replaceAll(Pattern.quote("\\"), Matcher.quoteReplacement("\\\\"))

Nawet lepiej

Jeśli naprawdę nie potrzebujemy obsługi składni wyrażeń regularnych, nie angażujmy się replaceAllw ogóle. Zamiast tego użyjmy replace. Obie metody zastąpią wszystkie target s, ale replacenie obejmują składni wyrażeń regularnych. Możesz więc po prostu napisać

theString = theString.replace("\\", "\\\\");
Pshemo
źródło
7

Będziesz musiał pominąć (uciekający) ukośnik odwrotny w pierwszym argumencie, ponieważ jest to wyrażenie regularne. Zastąpienie (drugi argument - zobacz Matcher # replaceAll (String) ) również ma specjalne znaczenie odwrotnych ukośników, więc będziesz musiał zamienić je na:

theString.replaceAll("\\\\", "\\\\\\\\");
sfussenegger
źródło
3

Tak ... zanim kompilator regex zobaczy wzorzec, który mu podałeś, widzi tylko jeden ukośnik odwrotny (ponieważ lekser Javy zamienił podwójny backwhack na pojedynczy). Trzeba wymienić "\\\\"z "\\\\"wierzyć lub nie! Java naprawdę potrzebuje dobrej składni nieprzetworzonych łańcuchów.

Jonathan Feinberg
źródło