Substituição estranha de barragem em rubi
Pergunta
Eu não entendo esse código de rubi:
>> puts '\\ <- single backslash'
# \ <- single backslash
>> puts '\\ <- 2x a, because 2 backslashes get replaced'.sub(/\\/, 'aa')
# aa <- 2x a, because two backslashes get replaced
Até agora, tudo o esperado. Mas se procurarmos por 1 com /\\/
, e substitua por 2, codificado por '\\\\'
, por que entendemos isso:
>> 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ó temos 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 de barriga é engolida na sequência de substituição? Isso acontece em 1.8 e 1.9.
Solução
Isso é um problema porque a barra de barriga () serve como um caractere de fuga para regexps e strings. Você pode usar a variável especial & para reduzir as barras -barras de números na corda 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 de barra.
Além disso, pensei que havia uma maneira especial de criar uma string que desativou o personagem Escape, mas aparentemente não. Nada disso produzirá duas barras:
puts "\\"
puts '\\'
puts %q{\\}
puts %Q{\\}
puts """\\"""
puts '''\\'''
puts <<EOF
\\
EOF
Outras dicas
Resposta rápida
Se você quiser evitar toda essa confusão, Use a sintaxe do bloco muito menos confuso. Aqui está um exemplo que substitui cada barragem por 2 barras de barriga:
"some\\path".gsub('\\') { '\\\\' }
Detalhes horríveis
O problema é que, ao usar sub
(e gsub
), sem um bloco, Ruby interpreta Sequências especiais de personagens no parâmetro de substituição. Infelizmente, sub
usa a barra de barriga como personagem de fuga 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 fuga, isso cria um problema óbvio. Se você deseja incluir o valor literal de uma das seqüências acima (por exemplo \1
) Na sequência de saída, você precisa escapar. Então, para conseguir Hello \1
, você precisa que a corda de substituição seja Hello \\1
. E para representar isso como uma corda literal em Ruby, você deve escapar daquelas barras de barriga novamente como esta: "Hello \\\\1"
Então, há Dois passes de fuga diferentes. O primeiro pega a string literal e cria o valor interno da string. O segundo leva esse valor interno da string e substitui as seqüências acima pelos dados correspondentes.
Se uma barra de barriga não for seguida por um personagem que corresponda a uma das seqüências acima, a barra de barragem (e o caractere a seguir) passará por inalterado. Isso também afeta uma barra de barriga no final da string - ela passará por inalterada. É mais fácil ver essa lógica no código Rubinius; basta procurar o to_sub_replacement
Método no Classe de string.
Aqui estão alguns exemplos de como String#sub
está analisando a sequência de substituição:
1 barra de barriga
\
(que tem uma corda literal de"\\"
)Passa por inalterado porque a barra de barriga está no final da corda e não tem caracteres depois dela.
Resultado:
\
2 barras de barriga
\\
(que têm uma corda literal de"\\\\"
)O par de barras de barriga corresponde à sequência de barra de barriga escape (veja
\\
acima) e é convertido em uma única barra de barriga.Resultado:
\
3 barras de barriga
\\\
(que têm uma corda literal de"\\\\\\"
)As duas primeiras barras de barriga correspondem ao
\\
sequência e seja convertido em uma única barra de barriga. Em seguida, a barragem final está no final da corda, por isso passa por inalterado.Resultado:
\\
4 barras de barriga
\\\\
(que têm uma corda literal de"\\\\\\\\"
)Dois pares de barras de barriga correspondem ao
\\
sequência e seja convertido em uma única barra de barriga.Resultado:
\\
2 barras de barriga com caráter no meio
\a\
(que têm uma corda literal de"\\a\\"
)o
\a
Não corresponde a nenhuma das seqüências de fuga, por isso é permitido passar por inalterado. A barra de barriga também é permitida.Resultado:
\a\
Observação: O mesmo resultado pode ser obtido de:
\\a\\
(com a corda literal:"\\\\a\\\\"
)
Em retrospectiva, isso poderia ter sido menos confuso se String#sub
tinha usado um personagem de fuga diferente. Depois, não haveria a necessidade de escapar duas vezes todas as barras.
Argh, logo depois que eu digitei tudo isso, percebi que \
é usado para se referir a grupos na sequência de substituição. Eu acho que isso significa que você precisa de um literal \\
na corda de substituição para substituir uma \
. Para conseguir um literal \\
você precisa de quatro \
S, para substituir um por dois, você realmente precisa de oito (!).
# Double every occurrence of \. There's eight backslashes on the right there!
>> puts '\\'.sub(/\\/, '\\\\\\\\')
alguma coisa que estou perdendo? Algumas maneiras mais eficientes?
Limpando 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 de barriga não estão sendo substituídas aqui. Você está substituindo 1 escapei barragem com dois A's ('aa'). Isto é, se você usou .sub(/\\/, 'a')
, você só veria 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"