Вот способ согласно вопросу (Ruby 2).Это не красиво, и не совсем на 100% идеально во всех аспектах, но делает работу.
def newsub(str, *rest, &bloc)
str =~ rest[0] # => ArgumentError if rest[0].nil?
bloc.binding.tap do |b|
b.local_variable_set(:_, $~)
b.eval("$~=_")
end if bloc
str.sub(*rest, &bloc)
end
При этом, результат будет следующим:
_ = (/(xyz)/ =~ 'xyz')
p $1 # => "xyz"
p _ # => 0
p newsub("abcd", /ab(c)/, '\1') # => "cd"
p $1 # => "xyz"
p _ # => 0
p newsub("abcd", /ab(c)/){|m| $1} # => "cd"
p $1 # => "c"
p _ # => #<MatchData "abc" 1:"c">
v, _ = $1, newsub("efg", /ef(g)/){$1.upcase}
p [v, _] # => ["c", "G"]
p $1 # => "g"
p Regexp.last_match # => #<MatchData "efg" 1:"g">
In-анализ глубины
В определенном выше методе newsub
, когда задан блок, локальные переменные $ 1 и т. д. в потоке вызывающей стороны (пере) устанавливаются после выполнения блока, что согласуется с String#sub
.Однако, когда блок не задан, локальные переменные $ 1 и т. Д. не сбрасываются, тогда как в String#sub
, $ 1 и т. Д. Всегда сбрасываются независимо от того, задан блок или нет.
Кроме того, локальная переменная вызывающего _
сбрасывается в этом алгоритме.В соглашении Руби локальная переменная _
используется в качестве фиктивной переменной, и ее значение не следует читать или ссылаться.Поэтому это не должно вызывать каких-либо практических проблем.Если бы оператор local_variable_set(:$~, $~)
был действительным, временные локальные переменные не понадобились бы.Тем не менее, это не так, в Ruby (по крайней мере, начиная с версии 2.5.1).См. Комментарий (на японском языке) Казухиро НИШИЯМА в [ruby-list: 50708] .
Общий фон (спецификация Руби), объясненный
Вот простой примервыделите спецификацию Ruby, относящуюся к этой проблеме:
s = "abcd"
/b(c)/ =~ s
p $1 # => "c"
1.times do |i|
p s # => "abcd"
p $1 # => "c"
end
Специальные переменные $&
, $1
, $2
и т. д. (связанные, $~
(Regexp.last_match
), $'
и так) работают в местном масштабе.В Ruby локальная область наследует переменные с одинаковыми именами в родительской области.В приведенном выше примере переменная s
является унаследованной , как и $1
.Блок do
имеет выход -ed на 1.times
, а метод 1.times
не контролирует переменные внутри блока, за исключением параметров блока (i
в примере выше; nb , хотя Integer#times
не предоставляет никаких параметров блока, попытка получить один (ие) в блоке будет игнорироваться).
Это означает, что метод дает -sa блок не контролирует $1
, $2
и т. Д. В блоке, которые являются локальными переменными (даже если они могут выглядеть как глобальные переменные).
Case of String # sub
Теперь давайте проанализируем, как String#sub
с блоком работает:
'abc'.sub(/.(.)./){ |m| $1 }
Здесь метод sub
сначала выполняет совпадение с регулярным выражением, и, следовательно, локальные переменные, такие как $1
автоматически устанавливаются.Затем они (переменные типа $1
) наследуются в блоке , поскольку этот блок находится в той же области, что и метод "sub" .Они не передаются из sub
в блок и отличаются от параметра блока m
(который соответствует строке или эквивалентен $&
).
Для этогопричина, если метод sub
определен в области действия , отличной от блока, метод sub
не контролирует локальные переменные внутри блока, включая $1
. другая область действия означает случай, когда метод sub
написан и определен с помощью кода Ruby, или на практике все методы Ruby, кроме некоторых, написаны не на Ruby, а на том же языке, что и использованный.написать интерпретатор Ruby.
Официальный документ Ruby's (версия 2.5.1) объясняется в разделе String#sub
:
В блочной форметекущая строка совпадения передается как параметр, и переменные, такие как $ 1, $ 2, $ `, $ & и $ ', будут установлены соответствующим образом.
Правильно.На практике методы, которые могут устанавливать и устанавливают специальные переменные, связанные с регулярным выражением, такие как $ 1, $ 2 и т. Д., Ограничены некоторыми встроенными методами, включая Regexp#match
, Regexp#=~
, Regexp#===
, String#=~
, String#sub
, String#gsub
, String#scan
, Enumerable#all?
и Enumerable#grep
.
Совет 1: String#split
, кажется, сбрасывает $~
ноль всегда.
Совет 2: Regexp#match?
иString#match?
не обновлять $~
и, следовательно, гораздо быстрее.
Вот небольшой фрагмент кода, чтобы подчеркнуть, как работает область:
def sample(str, *rest, &bloc)
str.sub(*rest, &bloc)
$1 # non-nil if matches
end
sample('abc', /(c)/){} # => "c"
p $1 # => nil
Здесь $1
в методе sample () устанавливается в str.sub
в той же области видимости.Это означает, что метод sample()
не сможет (просто) сослаться на $1
в переданном ему блоке.
Я отмечу утверждение в разделе Регулярного выражения официального документа Ruby (Ver.2.5.1)
Используя оператор =~
со строкой и регулярным выражением, глобальная переменная $~
устанавливается после успешного совпадения.
вводит в заблуждение, потому что
$~
является предопределенной локальной областью действия ( не глобальная переменная) и $~
устанавливается (возможно, ноль) независимо от того, успешна ли последняя попытка совпадения.
Тот факт, что переменные типа $~
и $1
не являются глобальными переменными, могутбыть немного запутанным.Но эй, это полезные записи, не так ли?