每个人都说eval是邪恶的,你应该用$()代替。但我遇到的情况是 unquoting 在$()内部的处理方式不同。
背景是我经常被包含空格的文件路径烧掉,所以引用所有这些路径。关于想知道我的所有可执行文件来自何处的更多偏执狂。更加偏执,不相信自己,所以能够显示我即将运行的创建命令。
下面我尝试使用eval与$()的变体,以及是否引用命令名称(因为它可以包含空格)
BIN_LS="/bin/ls"
thefile="arf"
thecmd="\"${BIN_LS}\" -ld -- \"${thefile}\""
echo -e "\n Running command '${thecmd}'"
$($thecmd)
Running command '"/bin/ls" -ld -- "arf"'
./foo.sh: line 8: "/bin/ls": No such file or directory
echo -e "\n Eval'ing command '${thecmd}'"
eval $thecmd
Eval'ing command '"/bin/ls" -ld -- "arf"'
/bin/ls: cannot access arf: No such file or directory
thecmd="${BIN_LS} -ld -- \"${thefile}\""
echo -e "\n Running command '${thecmd}'"
$($thecmd)
Running command '/bin/ls -ld -- "arf"'
/bin/ls: cannot access "arf": No such file or directory
echo -e "\n Eval'ing command '${thecmd}'"
eval $thecmd
Eval'ing command '/bin/ls -ld -- "arf"'
/bin/ls: cannot access arf: No such file or directory
$("/bin/ls" -ld -- "${thefile}")
/bin/ls: cannot access arf: No such file or directory
所以......这令人困惑。引用的命令路径在$()构造内除外是有效的吗?一个更短,更直接的例子:
$ c="\"/bin/ls\" arf"
$ $($c)
-bash: "/bin/ls": No such file or directory
$ eval $c
/bin/ls: cannot access arf: No such file or directory
$ $("/bin/ls" arf)
/bin/ls: cannot access arf: No such file or directory
$ "/bin/ls" arf
/bin/ls: cannot access arf: No such file or directory
如何解释简单的$($c)
案例?
答案 0 :(得分:10)
使用"
引用单词是您与Bash交互的一部分。当您键入
$ "/bin/ls" arf
在提示符处或在脚本中,您告诉Bash该命令由单词/bin/ls
和arf
组成,而双引号则强调/bin/ls
是一个单词。
键入
时$ eval '"/bin/ls" arf'
你告诉Bash该命令包含单词eval
和"/bin/ls" arf
。由于eval
的目的是假装它的参数是一个实际的人工输入命令,这相当于运行
$ "/bin/ls" arf
并且"
的处理方式与提示一样。
请注意,此伪装特定于eval
; Bash通常不会假装某些东西是真正的人类型命令。
键入
时$ c='"/bin/ls" arf'
$ $c
$c
被替换,然后经历单词拆分(请参阅§3.5.7 "Word Splitting" in the Bash Reference Manual),因此命令的单词为"/bin/ls"
(注意双 - 引号!)和arf
。不用说,这不起作用。 (它也不是很安全,因为除了分词之外,$c
还会经历文件名扩展等等。通常你的参数扩展应该总是双引号,如果它们不能,那么你应该重写你的代码,以便它们可以。不带引号的参数扩展会引起麻烦。)
键入
时$ c='"/bin/ls" arf'
$ $($c)
这与以前相同,但现在您还尝试将非工作命令的输出用作 new 命令。不用说,这不会导致非工作命令突然发挥作用。
正如Ignacio Vazquez-Abrams在他的回答中所说,正确的解决方案是使用数组,并正确处理引用:
$ c=("/bin/ls" arf)
$ "${c[@]}"
将c
设置为包含两个元素/bin/ls
和arf
的数组,并将这两个元素用作命令的单词。
答案 1 :(得分:4)
事实上它首先没有意义。 Use an array instead.
$ c=("/bin/ls" arf)
$ "${c[@]}"
/bin/ls: cannot access arf: No such file or directory
答案 2 :(得分:4)
来自man page for bash,关于eval
:
eval [arg ...]: args被读取并连接成一个命令。 然后,shell将读取并执行此命令及其退出 status返回为eval的值。
当c
被定义为"\"/bin/ls\" arf"
时,外引号将导致整个事物被处理为eval
的第一个参数,这是预期的成为一个命令或程序。您需要传递eval
参数,以便单独列出目标命令及其参数。
$(...)
构造的行为与eval
不同,因为它不是带参数的命令。它可以立即处理整个命令,而不是一次处理一个参数。
关于原始前提的说明:人们说eval
是邪恶的主要原因是脚本通常使用它来执行用户提供的字符串作为shell命令。虽然有时很方便,但这是一个主要安全问题(在执行字符串之前通常没有实用的安全检查方法)。如果您在脚本中对硬编码字符串使用eval
,则安全问题不适用。但是,在脚本中使用$(...)
或`...`
进行命令替换通常更简单,更清晰,不会为eval
留下真正的用例。