Shell 中的 eval 有哪些有用的用例?

Nor*_*tfi 15 shell eval

您经常听到它eval is evil,无论是在 Shell/POSIX 世界中,还是在其他语言(如 python 等)中...

但我想知道,它真的没用吗?或者是否有一些神秘的、未记录的、有趣的或只是有用的用例?

如果答案以sh/bash为中心,我会更喜欢,但如果它也与其他壳有关,那也没关系。

PS:我 知道 为什么EVAL被认为evil

mur*_*uru 11

我知道两个......常见......用例用于eval

  1. 参数处理getopt

    [T]他的实现可以生成引用的输出,它必须再次被shell解释(通常使用eval命令)。

  2. 设置SSH 代理

    [T]代理打印所需的 shell 命令(可以生成 sh(1) 或 csh(1) 语法),这些命令可以在调用 shell 中进行评估,例如eval `ssh-agent -s`对于 Bourne 类型的 shell,例如 sh(1) 或 ksh( 1) 以及eval `ssh-agent -c`csh(1) 和衍生工具。

两种用途可能都有替代方案,但我不会在看到它们中的任何一个时眨眼。

  • @NordineLotfi,`ssh-agent` 打印类似`SSH_AGENT_PID=32442; 导出 SSH_AGENT_PID; echo Agent pid 32442;`,如果没有 eval,变量赋值_不_工作。尝试例如`$(echo FOO=123)`。虽然如果它确实输出了 `export FOO=123 BAR=456`,那会起作用,但是它不能让 shell 运行 `echo`(它不能自己打印它,因为输出被命令捕获代换)。此外,如果没有 eval,它就不能使用引号来获得带有空格的值,而不是它通常需要这样做。 (6认同)
  • 请注意,如果 IFS 可能是一个问题,您应该使用 `eval "$(ssh-agent -s)"`。此外,我认为 `getopts` 是作为 `getopt` 的 eval-free 替代品而存在的。 (2认同)

Qua*_*odo 9

要在没有像 Bash & Co. slicing (ie ${@: -1})这样的扩展的 POSIX shell 中获取最后一个参数,可以使用

eval "v=\${$#}" 
Run Code Online (Sandbox Code Playgroud)

$# 不受讨厌的技巧的影响,因为它是 shell 内部的,并且只能包含脚本/函数的参数数量。

我没有想出那个,是Stéphane Chazelas 在评论中这个答案中还提到了为什么以及何时应该避免使用 eval ?.


gle*_*man 6

使用 bash,因为Brace Expansion 发生在 Shell Parameter Expansion之前

$ char="F"
$ range=( {A.."$char"} )
$ declare -p range
declare -a range=([0]="{A..F}")
$ eval "range=( {A..$char} )"
$ declare -p range
declare -a range=([0]="A" [1]="B" [2]="C" [3]="D" [4]="E" [5]="F")
Run Code Online (Sandbox Code Playgroud)


LL3*_*LL3 6

我自己的“现实世界”用例的一些示例,在这些用例中,我无法想出更好的替代方案,eval只能巧妙地完成工作。


“条件扩展”用例。在这里,我想仅在$rmsg_pfx具有某些值时才使用重定向:

eval 'printf -- %s%s\\n "$rmsg_pfx" "$line" '"${rmsg_pfx:+>&2}"
Run Code Online (Sandbox Code Playgroud)

我不能没有它,eval因为那么该>&2位将作为参数扩展printf而不是作为它的重定向。

我可以改为复制该行以说明是否$rmsg_pfx为空,但这将是.. 好吧.. 代码重复。


说到重定向,作为一个“间接”用例,我喜欢依赖{varname}>&...重定向语法,我像下面这样模拟 POSIXly:

eval 'printf -- %s%s\\n "$rmsg_pfx" "$line" '"${rmsg_pfx:+>&2}"
Run Code Online (Sandbox Code Playgroud)

以上是关闭 fds,同样我正在做一个类似的间接模拟 fds 的打开。显然$rses_fd0$rses_fd1是脚本的内部变量,从头到尾完全在它的控制之下。


有时,我不得不eval简单地“保护”旨在针对特定 shell 而不会干扰其他 shell 的 shell 代码片段。

例如,下面的一段代码来自一个可移植的脚本(POSIXly),同时还嵌入了一些特定于 shell 的优化:

# equivalent of bash/ksh `exec {rses_fd0}>&- {rses_fd1}<&-` redirection syntax
eval "exec $rses_fd0>&- $rses_fd1<&-"
Run Code Online (Sandbox Code Playgroud)

dash 只是在词法级别上因未知(但直接)的语法而窒息,即使这种语法从未进入实际的代码路径。


另一个“保护”用例,在不同的意义上。有时我只是不想为保存和恢复目的而发明“不太可能”的名称。例如在下面的情况下,我只想$r保留 的值:

sochars='][ (){}:,!'"'\\"
# NOTE: wrapped in an eval to protect it from dash which croaks over the regex
eval 'o=; while [[ "$s" =~ ([^$sochars]*)([$sochars])(.*) ]]; do
    ...
done'
Run Code Online (Sandbox Code Playgroud)

我实际上经常使用上面的技巧来保留循环套件的退出状态,同时还进行清理操作,如下所示:

# wrapped in eval just to make sure that $r is not overwritten by (the call chain of) coolf
eval '
    coolf "$tmp" || return "$lerrno"'"
    return $r
"
Run Code Online (Sandbox Code Playgroud)

或者在与上述类似的情况下,作为“延迟执行”:

    done <&3
    eval "unset ret vals; exec 3<&-; return $?"
}
Run Code Online (Sandbox Code Playgroud)

请注意,上述两个片段的一个隐含意图是不从函数执行中留下“工件”,尤其是当函数旨在以交互方式运行时。对于后一种情况,我可以改为:

    done
    # return boolean set by loop while also unsetting it
    eval "unset ok; ${ok:-false}"
}
Run Code Online (Sandbox Code Playgroud)

但对我来说看起来很粗糙。


最后,我有一些偶尔的用例,我想/需要在调用的基础上稍微修改或扩展一个函数,也许是为了小的行为改变或支持来自调用者的一些钩子。类似于回调,但采用“内联”方式,这似乎不那么麻烦,尤其是当钩子片段需要访问函数自己的$@参数时。自然地,这样的片段,通过变量馈送,随后eval由函数进行处理,要么完全是静态的/手工制作的,要么是经过大量预控制/消毒的。

  • +1; 其中一半让我希望所有 shell 都有带有静态作用域的局部变量。第一个让我想起了这一点:https://unix.stackexchange.com/questions/38310/conditional-pipeline 顺便说一句,我无法让 Dash 因无效扩展而出错,`dash -c 'if false; 然后取消设置“${!trap_@}”;fi'` 运行良好。并不意味着它不会用其他一些语法扩展更快地燃烧。 (2认同)
  • @ilkkachu 感谢您指出这一点。是的,语法元素必须是词法元素,否则不会触发错误。诚然,我已经习惯于将特定于 shell 的语法包装在 `eval` 中作为安全默认值,即使它不需要。我现在报告了一个更好的例子,自`dash` v0.5.11.3 起有效 (2认同)