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

时间:2017-02-24 00:35:52

标签: bash ifs

我正在尝试使用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

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

> "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函数不受空格,全局和任何其他特殊字符的影响?

1 个答案:

答案 0 :(得分:6)

解释

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"

在“最佳做法”示例中,保证将tempdirpidfile的内容视为传递给rm的文件名,无论这些值是什么。

但是,使用“允许管道”示例,上述内容可以调用rm -rf -- | arbitrary-command-heretempfile='|'pidfile=arbitrary-command-here

由于shell变量是从存在的环境变量集初始化的,而环境变量 通常是外部可控的 - 正如Shellshock的远程攻击的存在所证明的那样 - 这是不是纯粹的理论或空闲问题。