如何在对存储在 $@ 中的命令 运行 使用 eval 时防止分词?

How to prevent word splitting while using eval to run a command stored in $@?

我正在尝试使用 eval 到 运行 通过 $@ 传递给函数的命令。

这是我的代码:

run_command() {
  : some logic not relevant to this question
  eval "$@"
}

我运行将其设置为:

run_command "ls" "|" "wc -l"  # works, runs "ls | wc -l"
run_command "ls | wc -l"      # works as above

现在,我尝试列出一个包含 space 的文件:

> "file with space"
run_command "ls" "-l" "file with space"

这一次,我得到了这些错误:

ls: file: No such file or directory
ls: space: No such file or directory
ls: with: No such file or directory

所以,很明显 "$@" 导致了分词。有没有办法防止这个问题,使 run_command 函数不受白色 space、glob 和任何其他特殊字符的影响?

说明

eval 将所有参数组合成一个字符串,并将该字符串作为代码求值。因此,eval "ls" "-l" "file with space"eval ls -l file with spaceeval "ls -l file with space".

完全相同

最佳实践(无管道支撑)

BashFAQ #50 中给出 -- 以下 运行 作为简单命令的参数向量的确切参数列表。

run_command() {
  "$@"
}

这提供了许多保证:

  • 不会发生进程替换、命令替换或其他不需要的操作。因此,可以传递任意文件名而不会导致其内容的任何部分被解析为代码的双重扩展。
  • 参数将始终传递给 运行 的程序,而不是由 shell 解析。因此,将传递具有确切字符串 >foo 的数组条目,而不是创建名为 foo 的文件。

如果您需要用管道包装命令,可以通过将该管道封装在一个函数中来完成:

run_pipeline() { foo "$@" | bar; }
run_command run_pipeline "argument one" "argument two"

允许管道作为直接参数

明确一点:我不建议使用此代码。通过使 | 免受以下最佳实践提供的通常保护,它削弱了上述实践提供的安全性。但是,它 确实 做您要求的事情。

run_command() {
  local cmd_str='' arg arg_q
  for arg; do
    if [[ $arg = "|" ]]; then
      cmd_str+=" | "
    else
      printf -v arg_q '%q' "$arg"
      cmd_str+=" $arg_q"
    fi
  done
  eval "$cmd_str"
}

在这种形式中,| 的参数将导致生成的字符串包含 复合命令,在该参数的位置拆分为简单的命令。


为什么这是个坏主意

现在 -- 为什么在这种情况下尝试允许处理语法元素是个坏主意?考虑以下因素:

echo '<hello>'

这里,字符串<hello>中的<>被引用了,不再有原来的行为。但是,一旦您将这些值分配给数组或参数列表,如

args=( 'echo' '<hello>' )

...关于哪些字符被引用或未被引用的元数据不再存在。因此,

echo hello '|' world

变得与

完全没有区别
echo hello | world

即使作为单独的命令,它们也会有非常不同的行为。


具体示例

考虑以下几点:

run_command rm -rf -- "$tempdir" "$pidfile"

在 "best practices" 示例中,保证将 tempdirpidfile 的内容都视为传递给 rm 的文件名,无论这些值是什么.

但是,对于 "allowing pipes" 示例,上面的代码可以改为调用 rm -rf -- | arbitrary-command-here,应该 tempfile='|'pidfile=arbitrary-command-here.

因为 shell 变量是从存在的环境变量集初始化的,并且环境变量 通常是外部可控的——正如远程攻击的存在所证明的那样Shellshock -- 这不是一个纯理论或无意义的问题。