Substituição de barra invertida estranha em Ruby

Eu não entendo esse código Ruby:

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

até agora, tudo como esperado. mas se procurarmos por 1 com /\\/ e replace por 2, codificado por '\\\\' , por que recebemos isto:

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

e então, quando codificamos 3 com '\\\\\\' , só recebemos 2:

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

Alguém capaz de entender por que uma barra invertida é engolida na seqüência de substituição? isso acontece em 1.8 e 1.9.

Isso é um problema porque a barra invertida (\) serve como um caractere de escape para Regexps e Strings. Você poderia usar a variável especial \ & para reduzir as barras invertidas dos números na string de substituição do gsub.

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

EDIT: devo mencionar que o valor de \ & é de uma correspondência Regexp, neste caso, uma única barra invertida.

Além disso, pensei que havia uma maneira especial de criar uma string que desativasse o caractere de escape, mas aparentemente não. Nenhum destes produzirá duas barras:

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

Resposta rápida

Se você quiser evitar toda essa confusão, use a syntax de bloco muito menos confusa . Aqui está um exemplo que substitui cada barra invertida por duas barras invertidas:

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

Detalhes horríveis

O problema é que ao usar sub (e gsub ), sem um bloco, o ruby ​​interpreta sequências de caracteres especiais no parâmetro de substituição. Infelizmente, sub usa a barra invertida como o caractere de escape para estes:

 \& (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) 

Como qualquer escape, isso cria um problema óbvio. Se você quiser include o valor literal de uma das seqüências acima (por exemplo, \1 ) na string de saída, você terá que escaping dela. Portanto, para obter o Hello \1 , é necessário que a string de substituição seja Hello \\1 . E para representar isso como uma string literal em Ruby, você tem que escaping dessas barras invertidas novamente assim: "Hello \\\\1"

Então, existem dois diferentes escapes . O primeiro leva a string literal e cria o valor da string interna. O segundo leva esse valor de seqüência de caracteres interno e substitui as seqüências acima com os dados correspondentes.

Se uma barra invertida não for seguida por um caractere que corresponda a uma das seqüências acima, a barra invertida (e o caractere que segue) passará inalterada. Isso também afeta uma barra invertida no final da string – ela passará inalterada. É mais fácil ver essa lógica no código rubynius; basta procurar o método to_sub_replacement na class String .

Aqui estão alguns exemplos de como String#sub está analisando a string de substituição:

  • 1 barra invertida \ (que tem uma string literal de "\\" )

    Passa inalterado porque a barra invertida está no final da string e não possui caracteres após ela.

    Resultado:

  • 2 barras invertidas \\ (que têm uma string literal de "\\\\" )

    O par de barras invertidas corresponde à sequência de barra invertida que escapou (consulte \\ acima) e é convertido em uma única barra invertida.

    Resultado:

  • 3 barras invertidas \\\ (que têm uma string literal de "\\\\\\" )

    As duas primeiras barras invertidas correspondem à sequência \\ e são convertidas em uma única barra invertida. Em seguida, a última barra invertida está no final da seqüência de caracteres para que ela passe inalterada.

    Resultado: \\

  • 4 barras invertidas \\\\ (que tem uma string literal de "\\\\\\\\" )

    Dois pares de barras invertidas combinam com a seqüência \\ e são convertidos em uma única barra invertida.

    Resultado: \\

  • 2 barras invertidas com caractere no meio \a\ (que tem uma string literal de "\\a\\" )

    O \a não corresponde a nenhuma das seqüências de escape, portanto, é permitido passar inalterado. A barra invertida final também é permitida.

    Resultado: \a\

    Nota: O mesmo resultado pode ser obtido de: \\a\\ (com a cadeia literal: "\\\\a\\\\" )

Em retrospectiva, isso poderia ter sido menos confuso se String#sub tivesse usado um caractere de escape diferente. Então não haveria a necessidade de duplicar todas as barras invertidas.

Depois que eu digitei tudo isso, percebi que \ é usado para se referir a grupos na string de substituição. Eu acho que isso significa que você precisa de um literal \\ na seqüência de substituição para obter um substituído \ . Para obter um literal \\ você precisa de quatro \ s, então para replace um por dois você realmente precisa de oito (!).

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

qualquer coisa que eu estou perdendo? alguma maneira mais eficiente?

Esclarecendo um pouco de confusão na segunda linha de código do autor.

Você disse:

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

2 barras invertidas não estão sendo substituídas aqui. Você está substituindo 1 barra invertida com dois a's ('aa'). Ou seja, se você usou .sub(/\\/, 'a') , você veria apenas um 'a'

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

o livro da picareta menciona esse problema exato, na verdade. aqui está outra alternativa (da página 130 da última edição)

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