Dziwne zastępowanie ukośnikiem w Ruby

Nie rozumiem tego kodu Ruby:

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

>> puts '\\ <- 2x a, because 2 backslashes get replaced'.sub(/\\/, 'aa')
# aa <- 2x a, because two backslashes get replaced
Jak na razie wszystko zgodnie z oczekiwaniami. ale jeśli poszukamy 1 za pomocą /\\/ i zamienimy na 2, zakodowane przez '\\\\', Dlaczego otrzymujemy to:
>> puts '\\ <- only 1 ... replace 1 with 2'.sub(/\\/, '\\\\')
# \ <- only 1 backslash, even though we replace 1 with 2

I wtedy, gdy kodujemy 3 za pomocą '\\\\\\', otrzymujemy tylko 2:

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

Ktoś jest w stanie zrozumieć, dlaczego ukośnik zostaje połknięty w łańcuch zastępczy? dzieje się tak na 1.8 i 1.9.

Author: Gilles, 2009-10-09

5 answers

Jest to problem, ponieważ odwrotny ukośnik ( \ ) służy jako znak specjalny dla wyrażeń regularnych i łańcuchów. Możesz użyć specjalnej zmiennej \&, aby zmniejszyć liczbę ukośników wstecznych w łańcuchu zastępczym gsub.

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

EDIT: powinienem wspomnieć, że wartość \ & pochodzi z dopasowania Regexp, w tym przypadku pojedynczego ukośnika.

Myślałem również, że istnieje specjalny sposób na stworzenie ciągu znaków, który wyłącza znak escape, ale najwyraźniej nie. Żadne z nich nie spowoduje dwóch cięć:

puts "\\"
puts '\\'
puts %q{\\}
puts %Q{\\}
puts """\\"""
puts '''\\'''
puts <<EOF
\\
EOF  
 17
Author: sanscore,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-09 07:21:49

Szybka Odpowiedź

Jeśli chcesz uniknąć tego zamieszania, użyj znacznie mniej mylącej składni bloku . Oto przykład, który zastępuje każdy ukośnik dwoma ukośnikami:

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

Makabryczne Szczegóły

Problem polega na tym, że podczas używania sub (i gsub), bez bloku, ruby interpretuje specjalne sekwencje znaków w parametrze zastępczym. Niestety, sub używa odwrotnego ukośnika jako znaku escape dla te:

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

Jak każda Ucieczka, stwarza to oczywisty problem. Jeśli chcesz umieścić wartość literałową jednej z powyższych sekwencji (np. \1) w ciągu wyjściowym, musisz ją uwolnić. Tak więc, aby uzyskać Hello \1, potrzebujesz zastępczego łańcucha Hello \\1. Aby reprezentować to jako ciąg znaków w Rubim, musisz ponownie uciec od tych ukośników w ten sposób: "Hello \\\\1"

Są więc dwa różne przejścia ucieczki . Pierwszy bierze literalny ciąg znaków i tworzy wewnętrzna wartość ciągu. Drugi pobiera tę wewnętrzną wartość łańcucha i zastępuje powyższe sekwencje pasującymi danymi.

Jeśli ukośnik wsteczny nie jest poprzedzony znakiem, który pasuje do jednej z powyższych sekwencji ,to ukośnik wsteczny (i znak, który następuje) przejdzie bez zmian. Ma to również wpływ na odwrotny ukośnik na końcu łańcucha-przejdzie on bez zmian. Najłatwiej zobaczyć tę logikę w kodzie rubiniusa; wystarczy poszukać metody to_sub_replacement w Klasa String .

Oto kilka przykładów jak String#sub parsuje łańcuch zastępczy:

  • 1 backslash \ (który ma ciąg znaków "\\")

    Przechodzi bez zmian, ponieważ odwrotny ukośnik znajduje się na końcu łańcucha i nie ma znaków po nim.

    wynik: \

  • 2 backslashes \\ (które mają ciąg znaków "\\\\")

    The pary ukośników pasują do sekwencji unikalnego ukośnika (zobacz \\ powyżej) i są konwertowane na pojedynczy ukośnik.

    wynik: \

  • 3 backslashes \\\ (które mają ciąg znaków "\\\\\\")

    Pierwsze dwa ukośniki pasują do sekwencji \\ i są konwertowane na pojedynczy ukośnik. Następnie ostatni ukośnik znajduje się na końcu łańcucha, więc przechodzi przez niezmieniony.

    wynik: \\

  • 4 backslashes \\\\ (które mają ciąg znaków "\\\\\\\\")

    Dwie pary ukośników pasują do sekwencji \\ i są konwertowane na pojedynczy ukośnik.

    wynik: \\

  • 2 ukośniki z charakterem w środku \a\ (które mają ciąg znaków "\\a\\")

    \a nie pasuje do żadnej z sekwencji, więc może przejść bez zmian. Tylny Ukośnik jest również dozwolony.

    wynik: \a\

    Uwaga: ten sam wynik można uzyskać z: \\a\\ (z ciągiem literalnym: "\\\\a\\\\")

Z perspektywy czasu, mogłoby to być mniej mylące, gdyby String#sub użyło innej postaci ucieczki. Wtedy nie byłoby potrzeby podwójnego unikania wszystkich ukośników.

 61
Author: two-bit-fool,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-10 21:08:12

Argh, zaraz po wpisaniu tego wszystkiego, zdałem sobie sprawę, że \ jest używany do odwoływania się do grup w łańcuchu zastępczym. Domyślam się, że oznacza to, że potrzebujesz dosłownego \\ w łańcuchu zastępczym, aby otrzymać jeden zastąpiony \. Aby uzyskać literał \\ potrzebujesz czterech \ s, więc aby zastąpić jeden z dwóch, potrzebujesz ośmiu(!).

# Double every occurrence of \. There's eight backslashes on the right there!
>> puts '\\'.sub(/\\/, '\\\\\\\\')
Coś mi umknęło? jakieś skuteczniejsze sposoby?
 4
Author: Peter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-09 07:02:36

Wyjaśnienie małego zamieszania w drugiej linijce kodu autora.

Powiedziałeś:

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

Dwa ukośniki nie są tu zastępowane. Zamieniasz 1 escaped backslash na dwa a ('aa'). Oznacza to, że jeśli użyjesz .sub(/\\/, 'a'), zobaczysz tylko jedno " a "

'\\'.sub(/\\/, 'anything') #=> anything
 3
Author: maček,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-09 19:22:21

Książka kilofa wspomina dokładnie o tym problemie. oto kolejna alternatywa (ze strony 130 najnowszego wydania)

str = 'a\b\c'               # => "a\b\c"
str.gsub(/\\/) { '\\\\' }   # => "a\\b\\c"
 1
Author: Peter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-09 07:30:21