Bash中的eval命令及其典型用法

kst*_*tis 152 linux bash shell scripting eval

阅读了bash手册页和关于这篇文章.

我仍然无法理解eval命令究竟是什么以及它的典型用途.例如,如果我们这样做:

bash$ set -- one two three  # sets $1 $2 $3
bash$ echo $1
one
bash$ n=1
bash$ echo ${$n}       ## First attempt to echo $1 using brackets fails
bash: ${$n}: bad substitution
bash$ echo $($n)       ## Second attempt to echo $1 using parentheses fails
bash: 1: command not found
bash$ eval echo \${$n} ## Third attempt to echo $1 using 'eval' succeeds
one
Run Code Online (Sandbox Code Playgroud)

这里究竟发生了什么,美元符号和反斜杠如何与问题相关联?

Gil*_*il' 188

eval将字符串作为其参数,并将其计算为就像在命令行上键入该字符串一样.(如果传递多个参数,它们首先会在它们之间用空格连接.)

${$n}是bash中的语法错误.在大括号内,你只能有一个变量名,带有一些可能的前缀和后缀,但你不能有任意的bash语法,特别是你不能使用变量扩展.有一种方法可以说"名称在这个变量中的变量的值",但是:

echo ${!n}
one
Run Code Online (Sandbox Code Playgroud)

$(…)运行子shell中括号内指定的命令(即在一个单独的进程中继承所有设置,例如当前shell中的变量值),并收集其输出.因此作为shell命令echo $($n)运行$n,并显示其输出.从$n评估到1,$($n)尝试运行1不存在的命令.

eval echo \${$n}运行传递给的参数eval.扩展后,参数是echo${1}.所以eval echo \${$n}运行命令echo ${1}.

需要注意的是大部分的时间,因此必须将变量替换和命令susbtitutions双引号(即随时有一个$)"$foo", "$(foo)".总是在变量和命令替换周围加上双引号,除非你知道你需要将它们关闭.如果没有双引号,shell将执行字段拆分(即将变量的值或命令的输出拆分为单独的单词),然后将每个单词视为通配符模式.例如:

$ ls
file1 file2 otherfile
$ set -- 'f* *'
$ echo "$1"
f* *
$ echo $1
file1 file2 file1 file2 otherfile
$ n=1
$ eval echo \${$n}
file1 file2 file1 file2 otherfile
$eval echo \"\${$n}\"
f* *
$ echo "${!n}"
f* *
Run Code Online (Sandbox Code Playgroud)

eval不经常使用.在某些shell中,最常见的用途是获取在运行时之前名称未知的变量的值.在bash中,由于${!VAR}语法原因,这不是必需的.eval当你需要构造一个包含运算符,保留字等的更长命令时,它仍然很有用.


Har*_*non 39

简单地将eval视为"在执行前一次评估你的表达"

eval echo \${$n}echo $1在第一轮评估之后成为.需要注意三点变化:

  • \$成为$(需要反斜杠,否则它会尝试来评估${$n}的,这意味着一个命名的变量{$n},这是不允许的)
  • $n 被评估为 1
  • eval消失

在第二轮中,它基本上echo $1可以直接执行.

因此,eval <some command>首先评估<some command>(通过评估这里我的意思是替换变量,用正确的替换转义字符等),然后再次运行结果表达式.

eval当您想要动态创建变量或从专门设计为可读的程序读取输出时使用.有关示例,请参见http://mywiki.wooledge.org/BashFAQ/048.该链接还包含一些使用的典型方式eval以及与之相关的风险.

  • 作为第一个项目符号的注释,允许使用`$ {VAR}`语法***,当有任何歧义时(首选`$ VAR == $ V`,后跟`AR`或` $ VAR == $ VA`后跟`R`).`$ {VAR}`相当于`$ VAR`.实际上,它是不允许的变量名称`$ n`. (3认同)
  • `eval eval echo \\\ $ {\ $ {$ i}}`会进行三重取消引用.我不确定是否有更简单的方法可以做到这一点.另外,`\ $ {$ n}`在我的机器上正常工作(打印`1`). (2认同)
  • @ Konos5`echo \\\ $ {\ $ {$ i}}`打印`\ $ {$ {n}}`.`eval echo \\\ $ {\ $ {$ i}}`相当于`echo\$ {$ {n}}``并打印`$ {1}`.`eval eval echo \\\ $ {\ $ {$ i}}`相当于`eval echo $ {1}`并打印`one`. (2认同)
  • @ Konos5沿着同样的思路思考 - 第一个`\`逃脱第二个,第三个`\`在那之后逃脱`$`.所以经过一轮评估后它变成了`\ $ {$ {n}}` (2认同)
  • @Konos5从左到右是考虑引用和反斜杠解析的正确方法.首先`\\`产生一个反斜杠.然后`\ $`收益1美元.等等. (2认同)

soo*_*oot 22

根据我的经验,eval的"典型"用法是运行生成shell命令来设置环境变量的命令.

也许您有一个使用环境变量集合的系统,并且您有一个脚本或程序可以确定应该设置哪些应用程序及其值.无论何时运行脚本或程序,它都在分叉的进程中运行,因此当它退出时,它直接对环境变量所做的任何事情都会丢失.但是该脚本或程序可以将导出命令发送到stdout.

如果没有eval,则需要将stdout重定向到临时文件,获取临时文件,然后将其删除.使用eval,您可以:

eval "$(script-or-program)"
Run Code Online (Sandbox Code Playgroud)

注意引号很重要.拿这个(人为的)例子:

# activate.sh
echo 'I got activated!'

# test.py
print("export foo=bar/baz/womp")
print(". activate.sh")

$ eval $(python test.py)
bash: export: `.': not a valid identifier
bash: export: `activate.sh': not a valid identifier
$ eval "$(python test.py)"
I got activated!
Run Code Online (Sandbox Code Playgroud)


小智 10

eval语句告诉shell将eval的参数作为命令并通过命令行运行它们.它在以下情况下很有用:

在您的脚本中,如果要将命令定义到变量中,稍后要使用该命令,则应使用eval:

/home/user1 > a="ls | more"
/home/user1 > $a
bash: command not found: ls | more
/home/user1 > # Above command didn't work as ls tried to list file with name pipe (|) and more. But these files are not there
/home/user1 > eval $a
file.txt
mailids
remote_cmd.sh
sample.txt
tmp
/home/user1 >
Run Code Online (Sandbox Code Playgroud)


cle*_*ght 7

更新:有人说应该 - 永远不要 - 使用 eval。我不同意。我认为当损坏的输入可以传递给eval. 但是,在许多常见情况下,这不是风险,因此在任何情况下都值得了解如何使用 eval。这个stackoverflow 答案解释了 eval 的风险和 eval 的替代方案。最终由用户决定 eval 是否/何时使用安全有效。


basheval语句允许您执行由 bash 脚本计算或获取的代码行。

也许最直接的例子是一个 bash 程序,它将另一个 bash 脚本作为文本文件打开,读取每一行文本,并按eval顺序执行它们。这与 bashsource语句的行为本质上是相同的,这是人们将使用的,除非有必要对导入的脚本的内容执行某种转换(例如过滤或替换)。

我很少需要eval,但我发现读取或写入名称包含在分配给其他变量的字符串中的变量很有用。例如,对变量集执行操作,同时保持较小的代码占用空间并避免冗余。

eval概念上很简单。但是,bash 语言的严格语法和 bash 解释器的解析顺序可能会发生细微差别,使之eval显得神秘且难以使用或理解。以下是要点:

  1. 传递给的参数是在运行时计算eval字符串表达式eval将其参数的最终解析结果作为脚本中的实际代码行执行。

  2. 语法和解析顺序是严格的。如果结果不是可执行的 bash 代码行,则在脚本范围内,程序将在eval尝试执行垃圾时在语句上崩溃。

  3. 测试时,您可以将eval语句替换为echo并查看显示的内容。如果它是当前上下文中的合法代码,则运行它eval会起作用。


以下示例可能有助于阐明 eval 的工作原理...

示例 1:

eval “正常”代码前面的语句是 NOP

$ eval a=b
$ eval echo $a
b
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,第一条eval语句没有任何意义,可以删除。eval第一行没有意义,因为代码没有动态方面,即它已经解析为 bash 代码的最后几行,因此它与 bash 脚本中的普通代码语句相同。第二个eval也是毫无意义的,因为虽然有一个解析步骤转换$a为其等效的文字字符串,但没有间接(例如,没有通过实际bash 名词或 bash 持有的脚本变量的字符串值进行引用),所以它的行为是相同的作为没有eval前缀的一行代码。



示例 2:

使用作为字符串值传递的 var 名称执行 var 赋值。

$ key="mykey"
$ val="myval"
$ eval $key=$val
$ echo $mykey
myval
Run Code Online (Sandbox Code Playgroud)

如果你是echo $key=$val,输出将是:

mykey=myval
Run Code Online (Sandbox Code Playgroud)

,作为字符串解析的最终结果,将被 eval 执行,因此最后的 echo 语句的结果......



示例 3:

向示例 2 添加更多间接

$ keyA="keyB"
$ valA="valB"
$ keyB="that"
$ valB="amazing"
$ eval eval \$$keyA=\$$valA
$ echo $that
amazing
Run Code Online (Sandbox Code Playgroud)

上面的例子比前面的例子稍微复杂一些,更多地依赖于 bash 的解析顺序和特性。该eval行将按以下顺序在内部粗略地解析(请注意,以下语句是伪代码,不是真正的代码,只是为了尝试展示该语句如何在内部分解为步骤以得出最终结果)

 eval eval \$$keyA=\$$valA  # substitution of $keyA and $valA by interpreter
 eval eval \$keyB=\$valB    # convert '$' + name-strings to real vars by eval
 eval $keyB=$valB           # substitution of $keyB and $valB by interpreter
 eval that=amazing          # execute string literal 'that=amazing' by eval
Run Code Online (Sandbox Code Playgroud)

如果假定的解析顺序不能充分解释 eval 的作用,则第三个示例可能会更详细地描述解析以帮助阐明发生了什么。



示例 4:

发现名称包含在字符串中的变量本身是否包含字符串值。

a="User-provided"
b="Another user-provided optional value"
c=""

myvarname_a="a"
myvarname_b="b"
myvarname_c="c"

for varname in "myvarname_a" "myvarname_b" "myvarname_c"; do
    eval varval=\$$varname
    if [ -z "$varval" ]; then
        read -p "$varname? " $varname
    fi
done
Run Code Online (Sandbox Code Playgroud)

在第一次迭代中:

varname="myvarname_a"
Run Code Online (Sandbox Code Playgroud)

Bash 将参数解析为eval,并eval在运行时看到字面意思:

eval varval=\$$myvarname_a
Run Code Online (Sandbox Code Playgroud)

下面的伪代码试图说明bash如何解释上面这行实际代码,以得出eval. (以下几行是描述性的,而不是确切的 bash 代码):

1. eval varval="\$" + "$varname"      # This substitution resolved in eval statement
2. .................. "$myvarname_a"  # $myvarname_a previously resolved by for-loop
3. .................. "a"             # ... to this value
4. eval "varval=$a"                   # This requires one more parsing step
5. eval varval="User-provided"        # Final result of parsing (eval executes this)
Run Code Online (Sandbox Code Playgroud)

一旦所有的解析完成,结果就是执行,它的效果是显而易见的,证明eval它本身并没有什么特别神秘的地方,复杂的是它的参数的解析

varval="User-provided"
Run Code Online (Sandbox Code Playgroud)

上面示例中的其余代码只是测试分配给 $varval 的值是否为空,如果是,则提示用户提供一个值。