Bash eval 替换 $() 并不总是等价的?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12789079/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Bash eval replacement $() not always equivalent?
提问by Shenme
Everybody says eval is evil, and you should use $() as a replacement. But I've run into a situation where the unquotingisn't handled the same inside $().
每个人都说 eval 是邪恶的,你应该使用 $() 作为替代。但是我遇到了这样一种情况,即$()中的取消引用处理方式不同。
Background is that I've been burned too often by file paths with spaces in them, and so like to quote all such paths. More paranoia about wanting to know where all my executables are coming from. Even more paranoid, not trusting myself, and so like being able to display the created commands I'm about to run.
背景是我经常被带有空格的文件路径烧毁,所以喜欢引用所有这些路径。更多关于想知道我所有的可执行文件来自哪里的偏执。更偏执,不相信自己,所以喜欢能够显示我将要运行的创建的命令。
Below I try variations on using eval vs. $(), and whether the command name is quoted (cuz it couldcontain spaces)
下面我尝试使用 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
So... this is confusing. A quoted command path is valid everywhere except inside a $() construct? A shorter, more direct example:
所以......这令人困惑。带引号的命令路径在 $() 结构内以外的任何地方都有效?一个更短、更直接的例子:
$ 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
How does one explain the simple $($c)case?
一个简单的$($c)案例如何解释 ?
回答by ruakh
The use of "to quote words is part of your interaction with Bash. When you type
使用"来引用单词是您与 Bash 交互的一部分。当你输入
$ "/bin/ls" arf
at the prompt, or in a script, you're telling Bash that the command consists of the words /bin/lsand arf, and the double-quotes are really emphasizing that /bin/lsis a single word.
在提示符或脚本中,您告诉 Bash 命令由单词/bin/ls和组成arf,而双引号实际上/bin/ls是在强调它是一个单词。
When you type
当你输入
$ eval '"/bin/ls" arf'
you're telling Bash that the command consists of the words evaland "/bin/ls" arf. Since the purpose of evalis to pretend that its argument is an actual human-input command, this is equivalent to running
你告诉 Bash 命令由单词eval和 组成"/bin/ls" arf。由于目的eval是假装它的参数是一个实际的人工输入命令,这相当于运行
$ "/bin/ls" arf
and the "gets processed just like at the prompt.
并且"就像在提示下一样进行处理。
Note that this pretense is specific to eval; Bash doesn't usually go out of its way to pretend that something was an actual human-typed command.
请注意,此伪装特定于eval; Bash 通常不会特意假装某事是实际的人工输入命令。
When you type
当你输入
$ c='"/bin/ls" arf'
$ $c
the $cgets substituted, and then undergoes word splitting(see §3.5.7 "Word Splitting" in the Bash Reference Manual), so the words of the command are "/bin/ls"(note the double-quotes!) and arf. Needless to say, this doesn't work. (It's also not very safe, since in addition to word-splitting, $calso undergoes filename-expansion and whatnot. Generally your parameter-expansions should always be in double-quotes, and if they can't be, then you should rewrite your code so they can be. Unquoted parameter-expansions are asking for trouble.)
将$c被取代,然后进行分词(见§3.5.7“分词”中的Bash参考手册),因此命令的话"/bin/ls"(注意双引号!)和arf。不用说,这行不通。(这也不是很安全,因为除了分词之外,$c还会进行文件名扩展等等。通常您的参数扩展应该始终用双引号引起来,如果不能,那么您应该重写代码所以他们可以。未加引号的参数扩展是自找麻烦。)
When you type
当你输入
$ c='"/bin/ls" arf'
$ $($c)
this is the same as before, except that now you're also trying to use the output of the nonworking command as a newcommand. Needless to say, that doesn't cause the nonworking command to suddenly work.
这与以前相同,除了现在您还尝试将非工作命令的输出用作新命令。不用说,这不会导致非工作命令突然起作用。
As Ignacio Vazquez-Abrams says in his answer, the right solution is to use an array, and handle the quoting properly:
正如 Ignacio Vazquez-Abrams 在他的回答中所说,正确的解决方案是使用数组,并正确处理引用:
$ c=("/bin/ls" arf)
$ "${c[@]}"
which sets cto an array with two elements, /bin/lsand arf, and uses those two elements as the word of a command.
它设置c为一个包含两个元素/bin/ls和的数组arf,并使用这两个元素作为命令的单词。
回答by Ignacio Vazquez-Abrams
With the fact that it doesn't make sense in the first place. Use an array instead.
事实上,它首先没有意义。请改用数组。
$ c=("/bin/ls" arf)
$ "${c[@]}"
/bin/ls: cannot access arf: No such file or directory
回答by bta
From the man page for bash, regarding eval:
eval [arg ...]: The args are read and concatenated together into a single command. This command is then read and executed by the shell, and its exit status is returned as the value of eval.
eval [arg ...]: args 被读取并连接到一个命令中。然后这个命令被shell读取并执行,它的退出状态作为eval的值返回。
When cis defined as "\"/bin/ls\" arf", the outer quotes will cause the entire thingto be processed as the first argument to eval, which is expected to be a command or program. You need to pass your evalarguments in such a way that the target command and its arguments are listed separately.
当c被定义为"\"/bin/ls\" arf",外引号将导致整个事情到作为第一个参数被处理eval,这是预期为一命令或程序。您需要以eval目标命令及其参数分开列出的方式传递参数。
The $(...)construct behaves differently than evalbecause it is not a command that takes arguments. It can process the entire command at once instead of processing arguments one at a time.
该$(...)构造的行为不同于eval因为它不是一个带参数的命令。它可以一次处理整个命令,而不是一次处理一个参数。
A note on your original premise: The main reason that people say that evalis evil was because it is commonly used by scripts to execute a user-provided string as a shell command. While handy at times, this is a majorsecurity problem (there's typically no practical way to safety-check the string before executing it). The security problem doesn't apply if you are using evalon hard-coded strings inside your script, as you are doing. However, it's typically easier and cleaner to use $(...)or `...`inside of scripts for command substitution, leaving no real use case left for eval.
关于您的原始前提的注释:人们说这eval是邪恶的主要原因是因为脚本通常使用它来执行用户提供的字符串作为 shell 命令。虽然有时很方便,但这是一个主要的安全问题(通常没有切实可行的方法在执行之前对字符串进行安全检查)。如果您eval在脚本中使用硬编码字符串,则安全问题不适用,正如您所做的那样。但是,使用脚本$(...)或`...`在脚本内部进行命令替换通常更容易、更清晰,不会为eval.

