Подтвердить что ты не робот

Замедленная замена обратной косой черты в 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.

4b9b3361

Ответ 1

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

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

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

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

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

Ответ 2

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

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

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

Ужасные детали

Проблема заключается в том, что при использовании subgsub) без блока 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"

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

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

Вот некоторые примеры того, как String#sub анализирует заменяющую строку:

  • 1 обратная косая черта \ (которая имеет строковый литерал "\\")

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

    Результат: \

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

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

    Результат: \

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

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

    Результат: \\

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

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

    Результат: \\

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

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

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

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

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

Ответ 3

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

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

все, что мне не хватает? более эффективными способами?

Ответ 4

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

Ты сказал:

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

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

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

Ответ 5

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

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