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

She*_*nme 9 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
Run Code Online (Sandbox Code Playgroud)

所以...这令人困惑.引用的命令路径在$()构造内除外是有效的吗?一个更短,更直接的例子:

$ 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
Run Code Online (Sandbox Code Playgroud)

如何解释这个简单的 $($c) 案例?

rua*_*akh 11

使用"引用单词是您与Bash交互的一部分.当你输入

$ "/bin/ls" arf
Run Code Online (Sandbox Code Playgroud)

在提示符或脚本中,你告诉Bash命令包含单词/bin/lsarf,而双引号实际上强调的/bin/ls是单个单词.

当你输入

$ eval '"/bin/ls" arf'
Run Code Online (Sandbox Code Playgroud)

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

$ "/bin/ls" arf
Run Code Online (Sandbox Code Playgroud)

并且"像处于提示符处一样处理.

请注意,这种伪装是特定的eval; Bash通常不会假装某些东西是真正的人类型命令.

当你输入

$ c='"/bin/ls" arf'
$ $c
Run Code Online (Sandbox Code Playgroud)

$c被取代,然后进行分词(见§3.5.7中"分词" 的Bash参考手册),所以命令字"/bin/ls"(注意双引号!)和arf.不用说,这不起作用.(它也不是很安全,因为除了分词之外,$c还会经历文件名扩展等等.通常你的参数扩展应该总是双引号,如果它们不能,那么你应该重写你的代码所以它们可以.不带引号的参数扩展正在寻找麻烦.)

当你输入

$ c='"/bin/ls" arf'
$ $($c)
Run Code Online (Sandbox Code Playgroud)

这和以前一样,除了现在您还尝试使用非工作命令的输出作为命令.不用说,这不会导致非工作命令突然起作用.

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

$ c=("/bin/ls" arf)
$ "${c[@]}"
Run Code Online (Sandbox Code Playgroud)

其设定c到一个数组与两个元件,/bin/lsarf,并使用这两种元素作为命令的字.


Ign*_*ams 5

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

$ c=("/bin/ls" arf)
$ "${c[@]}"
/bin/ls: cannot access arf: No such file or directory
Run Code Online (Sandbox Code Playgroud)


bta*_*bta 5

bash手册页,关于eval

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

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

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

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