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.
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
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.
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?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
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"
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