在击和其典型用途eval命令在击和其典型用途eval命令(eval command in Bash

2019-06-17 10:20发布

阅读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

究竟这里发生的一切,以及如何做的美元符号和反斜线扎入的问题?

Answer 1:

eval需要一个字符串作为它的参数,并评估它,就好像你键入的命令行上的字符串。 (如果您传递一些参数,它们首先与他们之间的间隔加入。)

${$n}是在bash语法错误。 大括号内,你只能有一个变量名,与一些可能的前缀和后缀,但你不能有任意的bash语法,特别是不能使用变量扩展。 人们说,虽然“他的名字是在这个变量的变量的值”的方法:

echo ${!n}
one

$(…)运行在一个子shell(即在单独的工艺继承如从当前shell变量值的所有设置)的括号内指定的命令,并收集其输出。 所以echo $($n)运行$n作为外壳命令,并显示其输出。 由于$n的计算结果为1$($n)尝试运行命令1 ,它不存在。

eval echo \${$n}运行传递给参数eval 。 在膨胀之后,参数是echo${1} 所以eval echo \${$n}运行命令echo ${1}

需要注意的是大部分的时间,因此必须将变量替换和命令替换双引号(即随时有一个$ ): "$foo", "$(foo)"始终把周围的变量和命令替换双引号 ,除非你知道你需要离开他们。 如果没有双引号,壳进行场分裂(即它将该变量或从命令输出到单独的字的值),然后将每个字作为通配符图案。 例如:

$ 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* *

eval不经常使用。 在一些贝壳,最常见的用途是获得他的名字不知道,直到运行时变量的值。 在bash中,这不是必要的感谢${!VAR}语法。 eval仍然是有用的,当你需要构建含有运营商,保留字等较长的命令



Answer 2:

简单地认为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被使用,以及与之相关的风险。



Answer 3:

根据我的经验,一个“典型”的使用eval是运行产生shell命令来设置环境变量的命令。

也许你有一个使用环境变量的集合的系统,你必须决定哪些应该设置及其值的脚本或程序。 当你运行一个脚本或程序,它运行在派生进程,因此它退出时任何它不直接对环境变量丢失。 但是,脚本或程序可以发送导出命令到标准输出。

如果没有的eval,您需要到标准输出重定向到一个临时文件,源临时文件,然后将其删除。 随着EVAL,你可以:

eval "$(script-or-program)"

注意引号是非常重要的。 借此(人为)例如:

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


Answer 4:

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 >


Answer 5:

更新:有人说应该-never-使用eval。 我不同意。 我认为可能出现风险时损坏输入可以传递给eval 但是也有许多共同的情况下,是不是风险,因此,这是值得知道如何在任何情况下使用eval。 这个计算器的答案解释EVAL和替代EVAL的风险。 最终是由用户来决定是否/何时EVAL是安全和有效使用。


在bash eval语句可以执行的计算出或者获取行代码,你的bash脚本。

也许最简单的例子是,打开另一个bash脚本为文本文件,读取每一行文本,并使用bash程序eval为了执行它们。 这是本质相同的行为在bash source说法,这是一个会用,除非有必要对进口脚本的内容进行某种变换(如过滤或替代)的。

我很少有需要eval ,但我认为它是有用读取或写入变量,其名称被包含在分配给其他变量的字符串。 例如,执行上的变量集动作,同时保持代码量小,避免冗余。

eval概念简单。 然而,在bash语言的严格的语法,并在bash解释器的解析顺序可以是细致入微,让eval显得神秘且难以使用或理解。 以下是要点:

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

  2. 语法和解析顺序是严格的。 如果结果不是bash的代码的可执行行,在脚本的范围,程序会在崩溃eval声明,因为它试图执行垃圾。

  3. 测试时可以更换eval with语句echo ,看看显示的内容。 如果是在目前情况下合法的代码,通过运行它eval会工作。


下面的例子可能有助于澄清如何EVAL作品...

实施例1:

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

$ eval a=b
$ eval echo $a
b

在上面的例子中,第一eval语句没有目的和可被消除。 eval是在第一行没有意义的,因为不存在对代码没有动态的方面,即,它已经解析成的bash的代码的最后行,从而可以将其用作的代码在bash脚本一个正常语句相同。 第二届eval是没有意义也是如此,因为,虽然有解析步转换$a到其字面等效字符串,没有间接(例如,通过一个实际的bash名词的字符串值或bash持有的脚本变量中没有引用),所以它将同样表现为没有一行代码eval前缀。



实施例2:

使用作为字符串值传递变种名称进行无功分配。

$ key="mykey"
$ val="myval"
$ eval $key=$val
$ echo $mykey
myval

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

mykey=myval

也就是说 ,作为字符串解析的最终结果,就是将EVAL执行,在最后的回声声明的,因此结果...



实施例3:

加入更多的间接实施例2

$ keyA="keyB"
$ valA="valB"
$ keyB="that"
$ valB="amazing"
$ eval eval \$$keyA=\$$valA
$ echo $that
amazing

以上是有点比前一个示例更复杂,依靠更重地解析阶和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

如果假定解析顺序并不能说明什么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

在第一次迭代:

varname="myvarname_a"

击解析的说法evaleval看到字面上这在运行时:

eval varval=\$$myvarname_a

下面的伪代码试图说明的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)

一旦所有的分析完成后,结果是在执行什么,它的效果是明显的,证明没有什么特别神秘eval本身和复杂性是其参数的解析

varval="User-provided"

该示例中的其余代码以上简单地测试以查看是否分配给$ varval值为空,并且,如果是这样,提示用户提供的值。



Answer 6:

我本来有意从来没有学会如何使用eval,因为大多数人会建议留远离它像瘟疫。 不过,我最近发现,让我捂脸不认识越早它的使用情况。

如果您有想要以交互方式运行,以测试cron作业,你可能会认为与猫该文件的内容,并复制并粘贴cron作业来运行它。 不幸的是,这涉及触摸鼠标,这是在我的书罪。

比方说,你有与内容/etc/cron.d/repeatme一个cron作业:

*/10 * * * * root program arg1 arg2

你不能执行此与在它前面的所有的垃圾剧本,但我们可以用切摆脱所有的垃圾,在一个子shell包装它,并执行与EVAL字符串

eval $( cut -d ' ' -f 6- /etc/cron.d/repeatme)

cut命令只打印出文件的第6场,由空格分隔。 EVAL然后执行该命令。

我在这里使用cron作业作为一个例子,但这个概念是格式化的标准输出的文本,然后评估该文本。

在这种情况下使用eval是不是不安全的,因为我们确切地知道我们会手前评估。



Answer 7:

我喜欢回答“执行前评估你的表达一个额外的时间”,并希望与另一个例子来阐明。

var="\"par1 par2\""
echo $var # prints nicely "par1 par2"

function cntpars() {
  echo "  > Count: $#"
  echo "  > Pars : $*"
  echo "  > par1 : $1"
  echo "  > par2 : $2"

  if [[ $# = 1 && $1 = "par1 par2" ]]; then
    echo "  > PASS"
  else
    echo "  > FAIL"
    return 1
  fi
}

# Option 1: Will Pass
echo "eval \"cntpars \$var\""
eval "cntpars $var"

# Option 2: Will Fail, with curious results
echo "cntpars \$var"
cntpars $var

选项2好奇的结果是,我们将已通过2个参数如下:

  • 第一个参数: "value
  • 第二个参数: content"

这是怎么回事的直觉? 额外eval将解决这个问题。

改编自https://stackoverflow.com/a/40646371/744133



Answer 8:

在这样的问题:

who | grep $(tty | sed s:/dev/::)

输出错误声称文件和TTY不存在。 我明白这意味着TTY没有被grep的执行之前的解释,而是说的bash通过TTY作为参数给grep,其解释为文件名。

还有嵌套重定向的情况,这应该由匹配括号来处理,应指定一个子进程,但庆典是原始地一个字分隔符,创建参数被发送到一个程序,所以括号不是第一个匹配的,但解释为看到。

我的具体使用grep,并指定该文件作为一个参数,而不是使用管道。 我还简化了基地命令,传递输出从命令作为一个文件,从而使I / O管道不会被嵌套:

grep $(tty | sed s:/dev/::) <(who)

效果很好。

who | grep $(echo pts/3)

是不是真的需要,但消除嵌套管和效果也不错。

总之,庆典似乎并不喜欢啄嵌套。 要明白的bash是不是写在一个递归的方式新浪潮程序是很重要的。 相反,bash的是一个古老的1,2,3方案,该方案已经附加了功能。 为了保证向后兼容性的目的,解释的初始方式从未被修改。 如果bash中被重写的第一场比赛括号,多少漏洞会被引入到庆典多少程序? 许多程序员喜欢被神秘。



Answer 9:

我最近不得不使用eval迫使多个花括号扩展在我需要的顺序进行评估。 bash所做的多支柱的扩张由左到右,所以

xargs -I_ cat _/{11..15}/{8..5}.jpg

扩展到

xargs -I_ cat _/11/8.jpg _/11/7.jpg _/11/6.jpg _/11/5.jpg _/12/8.jpg _/12/7.jpg _/12/6.jpg _/12/5.jpg _/13/8.jpg _/13/7.jpg _/13/6.jpg _/13/5.jpg _/14/8.jpg _/14/7.jpg _/14/6.jpg _/14/5.jpg _/15/8.jpg _/15/7.jpg _/15/6.jpg _/15/5.jpg

但我所需要的第二括号扩展首先进行,得到

xargs -I_ cat _/11/8.jpg _/12/8.jpg _/13/8.jpg _/14/8.jpg _/15/8.jpg _/11/7.jpg _/12/7.jpg _/13/7.jpg _/14/7.jpg _/15/7.jpg _/11/6.jpg _/12/6.jpg _/13/6.jpg _/14/6.jpg _/15/6.jpg _/11/5.jpg _/12/5.jpg _/13/5.jpg _/14/5.jpg _/15/5.jpg

最好我能想出这样做是

xargs -I_ cat $(eval echo _/'{11..15}'/{8..5}.jpg)

这工作,因为单引号的解析过程中保护首套自膨胀支架的eval命令行,使他们能够通过调用子shell扩展eval

有可能是涉及嵌套括号扩展,允许这一步发生了一些狡猾的计划,但如果我太老套而又愚蠢的看到它。



Answer 10:

你问到的典型应用。

关于shell脚本的一个常见的抱怨是,你(据说)不能按引用传递得到值回来了的功能。

但实际上,通过“EVAL”,你可以通过引用传递。 被叫方可以传回变量赋值的列表,以由呼叫方进行评估。 它是通过参考通过,因为主叫方可以允许指定结果变量(一个或多个)的名称(S) - 见下面的例子。 错误的结果可以被传回标准的名称,如错误号和errstr。

这里是通过引用传递在bash的例子:

#!/bin/bash
isint()
{
    re='^[-]?[0-9]+$'
    [[ $1 =~ $re ]]
}

#args 1: name of result variable, 2: first addend, 3: second addend 
iadd()
{
    if isint ${2} && isint ${3} ; then
        echo "$1=$((${2}+${3}));errno=0"
        return 0
    else
        echo "errstr=\"Error: non-integer argument to iadd $*\" ; errno=329"
        return 1
    fi
}

var=1
echo "[1] var=$var"

eval $(iadd var A B)
if [[ $errno -ne 0 ]]; then
    echo "errstr=$errstr"
    echo "errno=$errno"
fi
echo "[2] var=$var (unchanged after error)"

eval $(iadd var $var 1)
if [[ $errno -ne 0 ]]; then
    echo "errstr=$errstr"
    echo "errno=$errno"
fi  
echo "[3] var=$var (successfully changed)"

输出看起来是这样的:

[1] var=1
errstr=Error: non-integer argument to iadd var A B
errno=329
[2] var=1 (unchanged after error)
[3] var=2 (successfully changed)

有在文本输出几乎无限的带宽! 而且还有如果使用多条输出线更多的可能性:例如,第一行可用于变量赋值,第二个连续“思想流”,但是这超出了本文的范围。



文章来源: eval command in Bash and its typical uses