我很好奇R是否可以使用它的eval()函数来执行由字符串等提供的计算。

这是一个常见的情况:

eval("5+5")

然而,我得到的不是10:

[1] "5+5"

有解决方案吗?


当前回答

我同意有关于eval和parse的担忧,但如果表达式在字符串中,似乎没有什么可以做的。这个eval解析也被tidyverse专家在glue包中使用,参见https://github.com/tidyverse/glue/blob/d47d6c7701f738f8647b951df418cfd5e6a7bf76/R/transformer.R#L1-L12

也许eval(parse(text="5+5"))的可接受答案存在安全问题,如果文本字符串来自不受信任的来源,例如imagine user_input =" list.files()"或更糟糕的file.remove…

下面是一个潜在的解决方案。

The idea is to set the R environment in which the expression is to be evaluated. In R, most functions that 'comes with R' are actually in packages that gets autoloaded at R start up, eg 'list.files', 'library' and 'attach' functions come from the 'base' package. By setting the evaluation environment to empty environment, these functions are no longer available to the expression to be evaluated, preventing malicious code from executing. In the code below, by default I include only arithmetic related functions, otherwise user can provide the evaluation environment with explicitly allowed functions.

eval_text_expression <- function(text_expression, data_list, eval_envir = NULL) {
  # argument checks
  stopifnot(is.character(text_expression) && length(text_expression) == 1)
  stopifnot(is.list(data_list))
  stopifnot(length(data_list) == 0 || (!is.null(names(data_list)) && all(names(data_list) != "")))
  stopifnot(all(!(lapply(data_list, typeof) %in% c('closure', 'builtin'))))
  stopifnot(is.null(eval_envir) || is.environment(eval_envir))
  # default environment for convenience 
  if (is.null(eval_envir)) {
    arithmetic_funcs <- list("+" = `+`, "-" = `-`, "*" = `*`, "/" = `/`, "^" = `^`, "(" = `(`)
    eval_envir = rlang::new_environment(data = arithmetic_funcs, parent = rlang::empty_env())
  }
  # load data objects into evaluation environment, then evaluate expression
  eval_envir <- list2env(data_list, envir = eval_envir)
  eval(parse(text = text_expression, keep.source = FALSE), eval_envir)
}

eval_text_expression("(a+b)^c - d", list(a = 1, b = 2, c = 3, d = 4))
# [1] 23
eval_text_expression("list.files()", list())
# Error in list.files() : could not find function "list.files"
eval_text_expression("list.files()", list(), eval_envir = rlang::new_environment(list("list.files" = list.files)))
# succeeds in listing my files if i explicitly allow it

其他回答

不知道为什么没有人提到两个Base R函数专门做这件事:str2lang()和str2expression()。这些是parse()的变体,但似乎返回的表达式更清晰:

eval(str2lang("5+5"))

# > 10
  
eval(str2expression("5+5"))

# > 10

也想反驳那些海报上说的任何试图这样做的人都是错误的。我在R中读取作为文本存储在文件中的表达式,并试图计算它们。这些函数非常适合这个用例。

现在你也可以使用lazyeval包中的lazy_eval函数。

> lazyeval::lazy_eval("5+5")
[1] 10

或者,你可以使用我的pander包中的eval来捕获输出和所有警告、错误和其他消息以及原始结果:

> pander::evals("5+5")
[[1]]
$src
[1] "5 + 5"

$result
[1] 10

$output
[1] "[1] 10"

$type
[1] "numeric"

$msg
$msg$messages
NULL

$msg$warnings
NULL

$msg$errors
NULL


$stdout
NULL

attr(,"class")
[1] "evals"

可以使用parse()函数将字符转换为表达式。你需要指定输入为文本,因为parse默认期望一个文件:

eval(parse(text="5+5"))

eval()函数计算一个表达式,但"5+5"是一个字符串,而不是表达式。使用parse() with text=<string>将字符串更改为表达式:

> eval(parse(text="5+5"))
[1] 10
> class("5+5")
[1] "character"
> class(parse(text="5+5"))
[1] "expression"

调用eval()会调用许多行为,其中一些行为并不明显:

> class(eval(parse(text="5+5")))
[1] "numeric"
> class(eval(parse(text="gray")))
[1] "function"
> class(eval(parse(text="blue")))
Error in eval(expr, envir, enclos) : object 'blue' not found

参见tryCatch。