Bash eval替换$()并不总是等价?

时间:2012-10-08 20:35:11

标签: bash eval substitution quote

每个人都说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)案例?

3 个答案:

答案 0 :(得分:10)

使用"引用单词是您与Bash交互的一部分。当您键入

$ "/bin/ls" arf

在提示符处或在脚本中,您告诉Bash该命令由单词/bin/lsarf组成,而双引号则强调/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/lsarf的数组,并将这两个元素用作命令的单词。

答案 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留下真正的用例。