Оценить строку без интерполяции строк

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,

Другие вопросы по тегам