Странная замена обратной косой черты в Ruby

Я не понимаю этот код Ruby:

>> puts ' <- single backslash'
#  <- single backslash

>> puts ' <- 2x a, because 2 backslashes get replaced'.sub(//, 'aa')
# aa <- 2x a, because two backslashes get replaced

пока все, как и ожидалось. но если мы ищем 1 с //, и заменить на 2, закодированный '\', Почему мы получаем это:

>> puts ' <- only 1 ... replace 1 with 2'.sub(//, '\')
#  <- only 1 backslash, even though we replace 1 with 2

и затем, когда мы кодируем 3 с '\', мы получаем только 2:

>> puts ' <- only 2 ... 1 with 3'.sub(//, '\')
#  <- 2 backslashes, even though we replace 1 with 3

кто-нибудь может понять, почему обратная косая черта проглатывается в строке замены? это происходит на 1.8 и 1.9.

5 ответов


это проблема, потому что обратная косая черта (\) является Escape-символом для регулярных выражений и строк. Вы можете использовать специальную переменную \&, чтобы уменьшить количество обратных косых черт в строке замены gsub.

foo.gsub(/\/,'\&\&\&') #for some string foo replace each \ with \\

EDIT: я должен упомянуть, что значение \& из совпадения с регулярным выражением, в этом случае одна обратная косая черта.

кроме того, я думал, что есть специальный способ создать строку, которая отключила escape-символ, но, по-видимому, нет. Ни один из этих произведите две косые черты:

puts "\"
puts '\'
puts %q{\}
puts %Q{\}
puts """\"""
puts '''\'''
puts <<EOF
\
EOF  

Быстрый Ответ

если вы хотите обойти всю эту путаницу,использовать гораздо менее запутанный синтаксис блока. Вот пример, который заменяет каждую обратную косую черту двумя обратными косыми чертами:

"some\path".gsub('\') { '\\' }

Ужасные Подробности

проблема в том, что при использовании subgsub), без блока, ruby интерпретирует специальные последовательности символов в параметре замены. К сожалению, sub использует обратную косую черту как Escape-символ для этих:

\& (the entire regex)
\+ (the last group)
\` (pre-match string)
\' (post-match string)
 (same as \&)
 (first captured group)
 (second captured group)
\ (a backslash)

как любой побег, это создает очевидную проблему. Если вы хотите включить литеральное значение одной из вышеуказанных последовательностей (например,) в выходной строке вы должны избежать его. Итак, чтобы получить Hello , вам нужна строка замены, чтобы быть Hello \1. И чтобы представить это как строковый литерал в Ruby, вам нужно снова избежать этих обратных косых черт:"Hello \\1"

Итак, есть два разных побега проходит. Первый принимает строковый литерал и создает внутреннее строковое значение. Второй принимает это внутреннее строковое значение и заменяет последовательности выше соответствующими данными.

если за обратной косой чертой не следует символ, соответствующий одной из вышеуказанных последовательностей, то обратная косая черта (и следующий за ней символ) будет проходить без изменений. Это также влияет на обратную косую черту в конце строки - она будет проходить без изменений. Это проще всего увидеть логика в коде рубиниус, просто искать to_sub_replacement метод строковый класс.

здесь примеры как String#sub анализирует строку замены:

  • 1 слеш \ (который имеет строковый литерал "\")

    проходит без изменений, потому что обратная косая черта находится в конце строки и не имеет символов после нее.

    результат: \

  • 2 символы \ (которые имеют строковый литерал "\\")

    пара слешей матч escape-последовательности обратная косая черта (см. \ выше) и преобразуется в одну обратную косую черту.

    результат: \

  • 3 символы \\ (которые имеют строковый литерал "\\\")

    первые две косые черты матч \ последовательность и преобразуется в одну обратную косую черту. Затем последняя обратная косая черта находится в конце строки, поэтому она проходит через неизмененную.

    результат: \

  • 4 символы \\ (которые имеют строковый литерал "\\\\")

    две пары обратных косых черт каждый матч \ последовательность и получить преобразованы в один обратная косая черта.

    результат: \

  • 2 косых черты с символом в середине \a\ (которые имеют строковый литерал "\a\")

    на \a не соответствует ни одной из escape-последовательностей, поэтому разрешено проходить через неизмененный. Также разрешена обратная косая черта.

    результат: \a\

    Примечание: то же результат может быть получен из:\a\ (С литеральной строки: "\\a\\")

оглядываясь назад, это могло бы быть менее запутанным, если String#sub использовал другой escape-символ. Тогда не было бы необходимости дважды избегать всех обратных косых черт.


Аргх, сразу после того, как я напечатал все это, я понял, что \ используется для ссылки на группы в строке замены. Я думаю, это означает, что вам нужен буквальный \ в строке замены, чтобы получить один заменить \. Чтобы получить буквальное \ нужно четыре!--1-->s, поэтому для замены одного на два вам действительно нужно восемь(!).

# Double every occurrence of \. There's eight backslashes on the right there!
>> puts '\'.sub(/\/, '\\\\')

что я упускаю? есть более эффективные способы?


прояснение небольшой путаницы во второй строке кода автора.

Вы сказали:

>> puts '\ <- 2x a, because 2 backslashes get replaced'.sub(/\/, 'aa')
# aa <- 2x a, because two backslashes get replaced

2 обратные косые черты не заменяются здесь. Вы заменяете 1 убежал обратная косая черта с двумя a ('aa'). То есть, если вы использовали .sub(/\/, 'a'), вы увидите только один "a"

'\'.sub(/\/, 'anything') #=> anything

в книге кирки упоминается эта точная проблема, на самом деле. вот еще одна альтернатива (со страницы 130 последнего издания)

str = 'a\b\c'               # => "a\b\c"
str.gsub(/\/) { '\\' }   # => "a\b\c"