Быстрый ответ
Если вы хотите обойти эту путаницу, используйте намного менее запутанный синтаксис блока . Вот пример, который заменяет каждый обратный слеш двумя обратными слешами:
"some\\path".gsub('\\') { '\\\\' }
Ужасные детали
Проблема в том, что при использовании sub
(и gsub
) без блока ruby интерпретирует специальные последовательности символов в параметре замены. К сожалению, sub
использует обратную косую черту в качестве escape-символа для них:
\& (the entire regex)
\+ (the last group)
\` (pre-match string)
\' (post-match string)
\0 (same as \&)
\1 (first captured group)
\2 (second captured group)
\\ (a backslash)
Как и любой побег, это создает очевидную проблему. Если вы хотите включить литеральное значение одной из вышеуказанных последовательностей (например, \1
) в выходную строку, вы должны экранировать ее. Итак, чтобы получить Hello \1
, вам нужна строка замены, равная Hello \\1
. И чтобы представить это как строковый литерал в Ruby, вы должны снова избежать обратной косой черты следующим образом: "Hello \\\\1"
Итак, есть двух разных проходов . Первый берет строковый литерал и создает внутреннее строковое значение. Вторая принимает это внутреннее строковое значение и заменяет приведенные выше последовательности на соответствующие данные.
Если за обратной косой чертой не следует символ, соответствующий одной из указанных выше последовательностей, то обратный слеш (и следующий за ним символ) будет проходить без изменений. Это также влияет на обратную косую черту в конце строки - она будет проходить без изменений. Легче всего увидеть эту логику в коде Рубиниуса; просто найдите метод to_sub_replacement
в классе String .
Вот некоторые примеры того, как String#sub
выполняет синтаксический анализ строки замены:
1 обратная косая черта \
(со строковым литералом "\\"
)
Проходит без изменений, потому что обратная косая черта находится в конце строки и после нее нет символов.
Результат: \
2 обратной косой черты \\
(со строковым литералом "\\\\"
)
Пара обратных слешей соответствует последовательности экранированного обратного слэша (см. \\
выше) и преобразуется в один обратный слеш.
Результат: \
3 обратной косой черты \\\
(которые имеют строковый литерал "\\\\\\"
)
Первые две обратной косой черты соответствуют последовательности \\
и преобразуются в одну обратную косую черту. Затем последний обратный слеш находится в конце строки, поэтому он проходит через неизмененный.
Результат: \\
4 обратной косой черты \\\\
(которые имеют строковый литерал "\\\\\\\\"
)
Каждая пара обратной косой черты соответствует последовательности \\
и преобразуется в одну обратную косую черту.
Результат: \\
2 обратной косой черты с символом посередине \a\
(со строковым литералом "\\a\\"
)
\a
не соответствует ни одной из escape-последовательностей, поэтому ему разрешено проходить без изменений. Задний слеш также допускается.
Результат: \a\
Примечание: Тот же результат можно получить из: \\a\\
(с литеральной строкой: "\\\\a\\\\"
)
Оглядываясь назад, это могло бы быть менее запутанным, если бы String#sub
использовал другой escape-символ. Тогда не было бы необходимости дважды убегать от обратной косой черты.