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

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

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

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

如果答案以sh/bash为中心会更好,但如果它也与其他 Shell 相关也很好。

PS:我是出色地 意识到的 为什么考虑评估evil

答案1

我知道两个……常见的……用例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) 和衍生物。

这两种用途可能都有其他选择,但我不会对它们中的任何一个感到惊讶。

答案2

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

eval "v=\${$#}" 

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

这不是我想出来的,而是史蒂芬·查泽拉斯评论。中也提到了这个答案为什么以及何时应避免使用 eval?

答案3

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


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

eval 'printf -- %s%s\\n "$rmsg_pfx" "$line" '"${rmsg_pfx:+>&2}"

如果没有,我就无法做到这一点,eval因为这样该>&2位将作为参数展开,printf而不是作为其重定向。

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


说到重定向,作为“间接”用例,我喜欢依赖{varname}>&...重定向语法,我模拟 POSIXly,如下所示:

# equivalent of bash/ksh `exec {rses_fd0}>&- {rses_fd1}<&-` redirection syntax
eval "exec $rses_fd0>&- $rses_fd1<&-"

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


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

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

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

dash只是在词法级别上被未知(但直接)的语法所阻塞,即使此类语法从未进入实际的代码路径。


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

# 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
"

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

    done <&3
    eval "unset ret vals; exec 3<&-; return $?"
}

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

    done
    # return boolean set by loop while also unsetting it
    eval "unset ok; ${ok:-false}"
}

请注意,上面两个代码片段的一个隐含意图是不要在函数执行中留下“工件”,特别是当该函数旨在交互运行时。对于后一种情况我可以这样做:

[ "${ok:-false}" = false ] && { unset ok; return 1; } || { unset ok; return 0; }

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


最后,我有一些偶尔的用例,我想要/需要在调用的基础上稍微修改或扩展一个函数,也许是为了小的行为改变或支持调用者的一些挂钩。就像回调一样,但以“内联”方式,这看起来不那么麻烦,特别是当钩子片段需要访问函数自己的$@参数时。当然,这样的片段,通过变量提供,随后eval由函数编辑,它们本身要么是完全静态/手工制作的,要么是经过严格预先控制/清理的。

答案4

eval我遇到的 现实生活中的用法之一是用于Fluxbox.startfluxbox.dbus.diff.gz 在 Slackware 上。它看起来像这样:

# Start DBUS session bus:
if [ -z "$DBUS_SESSION_BUS_ADDRESS" ]; then
   eval $(dbus-launch --sh-syntax --exit-with-session)
fi

尽管这种用法eval尚未经过数百万人的测试(Slackware 并不常见),但它确实可以完成这项工作。尽管如此,我还是会尽力避免eval在我的 shell 脚本中出现这种情况。如果我感觉我需要它,例如实现数组或执行变量间接寻址,我会切换到 Bash,如果我仍然觉得我需要它,我会重新考虑脚本设计或切换到完全不同的语言。

相关内容