变量作为命令;评估与 bash -c

bie*_*iep 46 bash shell-script bash-script

我正在阅读某人制作的 bash 脚本,我注意到作者没有使用 eval 来评估变量作为命令
作者使用

bash -c "$1"
Run Code Online (Sandbox Code Playgroud)

代替

eval "$1"
Run Code Online (Sandbox Code Playgroud)

我认为使用 eval 是首选方法,无论如何它可能更快。真的吗?
两者之间有什么实际区别吗?两者之间有哪些显着差异?

Gil*_*il' 42

eval "$1"执行当前脚本中的命令。它可以设置和使用当前脚本的shell变量,设置当前脚本的环境变量,设置和使用当前脚本的函数,设置当前脚本的当前目录、umask、limits等属性等等。bash -c "$1"在完全独立的脚本中执行命令,该脚本继承环境变量、文件描述符和其他进程环境(但不回传任何更改)但不继承内部 shell 设置(shell 变量、函数、选项、陷阱等)。

还有另一种方法,(eval "$1")它在子shell 中执行命令:它继承调用脚本的所有内容,但不回传任何更改。

例如,假设变量dir未导出且$1cd "$foo"; ls,则:

  • cd /starting/directory; foo=/somewhere/else; eval "$1"; pwd列出内容/somewhere/else并打印/somewhere/else.
  • cd /starting/directory; foo=/somewhere/else; (eval "$1"); pwd列出内容/somewhere/else并打印/starting/directory.
  • cd /starting/directory; foo=/somewhere/else; bash -c "$1"; pwd列出内容/starting/directory(因为cd ""不会改变当前目录)并打印/starting/directory.


mik*_*erv 24

之间最重要的区别

bash -c "$1" 
Run Code Online (Sandbox Code Playgroud)

eval "$1"
Run Code Online (Sandbox Code Playgroud)

是前者在子shell中运行而后者不是。所以:

set -- 'var=something' 
bash -c "$1"
echo "$var"
Run Code Online (Sandbox Code Playgroud)

输出:

#there doesn't seem to be anything here
Run Code Online (Sandbox Code Playgroud)
set -- 'var=something' 
eval "$1"
echo "$var"
Run Code Online (Sandbox Code Playgroud)

输出:

something
Run Code Online (Sandbox Code Playgroud)

不过,我不知道为什么有人会bash以这种方式使用可执行文件。如果您必须调用它,请使用 POSIX 保证的内置sh. 或者,(subshell eval)如果您希望保护您的环境。

就个人而言,我更喜欢外壳.dot

printf 'var=something%d ; echo "$var"\n' `seq 1 5` | . /dev/fd/0
Run Code Online (Sandbox Code Playgroud)

输出

something1
something2
something3
something4
something5
Run Code Online (Sandbox Code Playgroud)

但是你真的需要它吗?

使用这两者的唯一原因实际上是在您的变量实际分配或评估另一个变量的情况下,或者分词对输出很重要。

例如:

var='echo this is var' ; $var
Run Code Online (Sandbox Code Playgroud)

输出:

this is var
Run Code Online (Sandbox Code Playgroud)

这有效,但仅仅是因为echo不关心它的参数数量。

var='echo "this is var"' ; $var
Run Code Online (Sandbox Code Playgroud)

输出:

"this is var"
Run Code Online (Sandbox Code Playgroud)

看?出现双引号是因为 shell 扩展 的结果$var没有针对 求值quote-removal

var='printf %s\\n "this is var"' ; $var
Run Code Online (Sandbox Code Playgroud)

输出:

"this
is
var"
Run Code Online (Sandbox Code Playgroud)

但与evalsh

    var='echo "this is var"' ; eval "$var" ; sh -c "$var"
Run Code Online (Sandbox Code Playgroud)

输出:

this is var
this is var
Run Code Online (Sandbox Code Playgroud)

当我们使用evalor 时sh,shell 对扩展的结果进行第二次传递,并将它们评估为潜在的命令,因此引号会有所不同。你也可以这样做:

. <<VAR /dev/fd/0
    ${var:=echo "this is var"}
#END
VAR
Run Code Online (Sandbox Code Playgroud)

输出

this is var
Run Code Online (Sandbox Code Playgroud)


Pla*_*wer 5

我做了一个快速测试:

time bash -c 'for i in {1..10000}; do bash -c "/bin/echo hi"; done'
time bash -c 'for i in {1..10000}; eval "/bin/echo hi"; done'
Run Code Online (Sandbox Code Playgroud)

(是的,我知道,我使用 bash -c 来执行循环,但这应该没什么区别)。

结果:

eval    : 1.17s
bash -c : 7.15s
Run Code Online (Sandbox Code Playgroud)

所以eval更快。从手册页eval

eval 实用程序应通过将参数连接在一起来构造命令,并用字符分隔每个参数。构造的命令应由 shell 读取和执行。

bash -c当然,在 bash shell 中执行命令。一个注意事项:我使用了/bin/echo因为echo是一个内置的shell bash,这意味着不需要启动一个新进程。更换/bin/echoechobash -c试验,花了1.28s。那是差不多的。但是,eval运行可执行文件的速度更快。这里的主要区别是eval不启动一个新的 shell(它在当前的 shell 中执行命令)而是bash -c启动一个新的 shell,然后在新的 shell 中执行命令。启动一个新的 shell 需要时间,这就是为什么bash -ceval.

  • 虽然这是真的,但它忽略了命令在不同环境中执行的根本区别。很明显,启动一个新的 bash 实例会更慢,这不是一个有趣的观察。 (3认同)