Оценить строку без интерполяции строк
AKA Как мне найти последовательность неэкранированных символов с помощью регулярных выражений?
Учитывая среду, настроенную с:
@secret = "OH NO!"
$secret = "OH NO!"
@@secret = "OH NO!"
и данная строка читается из файла, который выглядит следующим образом:
some_str = '"\"#{:NOT&&:very}\" bad. \u262E\n#@secret \\#$secret \\\\#@@secret"'
Я хочу оценить это как строку Ruby, но без интерполяции. Таким образом, результат должен быть:
puts safe_eval(some_str)
#=> "#{:NOT&&:very}" bad. ☮
#=> #@secret #$secret \#@@secret
В отличие от eval
решение дает
puts eval(some_str)
#=> "very" bad. ☮
#=> OH NO! #$secret \OH NO!
Сначала я попробовал:
def safe_eval(str)
eval str.gsub(/#(?=[{@$])/,'\\#')
end
но в приведенном выше злонамеренном среднем случае это не сработало, создав
#=> "#{:NOT&&:very}" bad. ☮
#=> #@secret \OH NO! \#@@secret
2 ответа
Вы можете сделать это с помощью регулярных выражений, убедившись, что перед символом, которого вы хотите экранировать, должно быть четное число обратных слэшей:
def safe_eval(str)
eval str.gsub( /([^\\](?:\\\\)*)#(?=[{@$])/, '\1\#' )
end
... который говорит:
- Найти персонажа, который не является обратной косой чертой
[^\\]
- с последующим двумя обратными слешами
(?:\\\\)
- повторяется ноль или более раз
*
- повторяется ноль или более раз
- с последующим буквальным
#
персонаж - и убедитесь, что после этого вы можете увидеть либо
{
,@
, или же$
персонаж. - и заменить это на
- не-обратная косая черта, возможно, сопровождаемая четным числом обратных косых черт
- а затем обратный слеш, а затем
#
Как насчет того, чтобы вообще не использовать eval? Согласно этому комментарию в чате, все, что необходимо, это экранировать кавычки, символы новой строки и символы Юникода. Вот мое решение:
ESCAPE_TABLE = {
/\\n/ => "\n",
/\\"/ => "\"",
}
def expand_escapes(str)
str = str.dup
ESCAPE_TABLE.each {|k, v| str.gsub!(k, v)}
#Deal with Unicode
str.gsub!(/\\u([0-9A-Z]{4})/) {|m| [m[2..5].hex].pack("U") }
str
end
При вызове вашей строки результат (в вашей переменной среде):
"\"\"\#{:NOT&&:very}\" bad. ☮\n\#@secret \\\#$secret \\\\\#@@secret\""
Хотя я бы предпочел не обрабатывать Unicode специально, это единственный способ сделать это без eval
,