12

每个人都说 eval 是邪恶的,你应该使用 $() 作为替代。但是我遇到了这样一种情况,即在 $()中对取消引用的处理方式不同。

背景是我经常被带有空格的文件路径烧伤,所以喜欢引用所有这些路径。更想知道我所有的可执行文件来自哪里的偏执狂。更偏执,不相信自己,所以喜欢能够显示我将要运行的创建的命令。

下面我尝试使用 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) 案例?

4

3 回答 3

14

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

$ "/bin/ls" arf

在提示符处或在脚本中,您是在告诉 Bash 该命令由单词/bin/lsand组成arf,而双引号实际上是在强调这/bin/ls是一个单词。

当您键入

$ eval '"/bin/ls" arf'

你告诉 Bash 命令由单词eval"/bin/ls" arf. 由于 的目的eval是假装它的参数是一个实际的人工输入命令,这相当于运行

$ "/bin/ls" arf

"像提示一样处理。

请注意,这种伪装是特定于eval; Bash 通常不会特意假装某些东西是真正的人工输入的命令。

当您键入

$ c='"/bin/ls" arf'
$ $c

$c替换,然后进行分词(请参阅Bash 参考手册中的第 3.5.7 节“分词”),因此命令的词是"/bin/ls"(注意双引号!)和arf. 不用说,这是行不通的。(这也不是很安全,因为除了分词之外,$c还会进行文件名扩展等。通常你的参数扩展应该总是用双引号引起来,如果不能,那么你应该重写你的代码所以他们可以。未引用的参数扩展正在自找麻烦。)

当您键入

$ c='"/bin/ls" arf'
$ $($c)

这与以前相同,只是现在您还尝试将非工作命令的输出用作命令。不用说,这不会导致非工作命令突然工作。

正如 Ignacio Vazquez-Abrams 在他的回答中所说,正确的解决方案是使用数组,并正确处理引用:

$ c=("/bin/ls" arf)
$ "${c[@]}"

它设置c为具有两个元素的数组,/bin/lsarf,并将这两个元素用作命令的单词。

于 2012-10-08T20:49:46.243 回答
5

事实上,它首先没有意义。请改用数组。

$ c=("/bin/ls" arf)
$ "${c[@]}"
/bin/ls: cannot access arf: No such file or directory
于 2012-10-08T20:47:34.297 回答
5

bash 的手册页中,关于eval

eval [arg ...]:args 被读取并连接到一个命令中。该命令随后被 shell 读取并执行,其退出状态作为 eval 的值返回。

c定义为"\"/bin/ls\" arf"时,外引号将导致整个事物作为 的第一个参数进行处理,该参数eval应为命令或程序。您需要以eval单独列出目标命令及其参数的方式传递参数。

$(...)构造的行为不同于eval因为它不是带参数的命令。它可以一次处理整个命令,而不是一次处理一个参数。

关于您原始前提的注释:人们说这eval是邪恶的主要原因是因为脚本通常使用它来执行用户提供的字符串作为 shell 命令。虽然有时很方便,但这是一个主要的安全问题(通常没有实用的方法在执行字符串之前对其进行安全检查)。eval如果您在脚本中使用硬编码字符串,则安全问题不适用,就像您正在做的那样。$(...)但是,使用或在脚本内部进行命令替换通常更容易和更干净`...`,没有留下真正的用例eval

于 2012-10-08T21:05:06.833 回答