Как уже говорили другие, вам нужно дважды избегать всего в этой строке. Таким образом, в вашем случае решение заключается в использовании '\\\\\1'
или '\\\\\\1'
. Но так как вы спросили почему, я постараюсь объяснить эту часть.
Причина в том, что последовательность замены анализируется дважды - один раз Ruby и один раз базовым механизмом регулярных выражений, для которого \1
является собственной escape-последовательностью. (Вероятно, это легче понять с помощью строк в двойных кавычках, поскольку одинарные кавычки вводят неоднозначность, в которой '\\1'
и '\1'
эквивалентны, а '\'
и '\\'
- нет.)
Так, например, простая замена здесь захваченной группой и строкой в двойных кавычках будет:
"foo+bar".gsub(/(\+)/, "\\1") #=> "foo+bar"
Это передает строку \1
в механизм регулярных выражений, который он понимает как ссылку на группу захвата. В строковых литералах Ruby "\1"
означает что-то совсем другое (символ ASCII 1).
В данном случае мы действительно хотим, чтобы механизм регулярных выражений получал \\\1
. Он также понимает \
как escape-символ, поэтому \\1
недостаточно и будет просто преобразован в буквальный вывод \1
. Итак, нам нужно \\\1
в движке регулярных выражений, но чтобы добраться до этой точки, нам нужно также пройти мимо строкового литерального анализатора Ruby.
Чтобы сделать это, мы берем желаемый ввод регулярных выражений и снова удваиваем каждый обратный слеш, чтобы пройти через синтаксический анализатор строковых литералов Ruby. \\\1
поэтому требуется "\\\\\\1"
. В случае одинарных кавычек одна косая черта может быть опущена, поскольку \1
не является допустимой escape-последовательностью в одинарных кавычках и трактуется буквально.
Добавление
Одна из причин, по которой эта проблема обычно скрывается, заключается в использовании кавычек регулярного выражения в стиле /.+/
, которые Ruby обрабатывает особым образом, чтобы избежать необходимости двойного экранирования всего. (Конечно, это не относится к gsub
замещающим строкам.) Но вы все равно можете увидеть это в действии, если вы используете строковый литерал вместо литерала регулярного выражения в Regexp.new
:
Regexp.new("\.").match("a") #=> #<MatchData "a">
Regexp.new("\\.").match("a") #=> nil
Как вы видите, нам пришлось дважды экранировать .
, чтобы он был понят как литерал .
с помощью механизма регулярных выражений, так как "."
и "\."
оба оценивают до .
в двойном в кавычках, но нам нужен сам двигатель для получения \.
.