阅读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
显得神秘且难以使用或理解。 以下是要点:
传递给自变量eval
是在运行时计算出的字符串表达式 。 eval
将执行其参数的最终分析结果作为代码的脚本中的实际行。
语法和解析顺序是严格的。 如果结果不是bash的代码的可执行行,在脚本的范围,程序会在崩溃eval
声明,因为它试图执行垃圾。
测试时可以更换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"
击解析的说法eval
和eval
看到字面上这在运行时:
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