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

lea*_*bee 2 bash ifs

我试图用来eval运行传递给函数的命令$@.

这是我的代码:

run_command() {
  : some logic not relevant to this question
  eval "$@"
}
Run Code Online (Sandbox Code Playgroud)

我正在运行它:

run_command "ls" "|" "wc -l"  # works, runs "ls | wc -l"
run_command "ls | wc -l"      # works as above
Run Code Online (Sandbox Code Playgroud)

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

> "file with space"
run_command "ls" "-l" "file with space"
Run Code Online (Sandbox Code Playgroud)

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

ls: file: No such file or directory
ls: space: No such file or directory
ls: with: No such file or directory
Run Code Online (Sandbox Code Playgroud)

因此,很明显"$@"会导致单词分裂.有没有办法防止这个问题,以便run_command功能免受白色空间,小球和任何其他特殊字符的影响?

Cha*_*ffy 6

说明

eval将所有参数组合成单个字符串,并将该字符串作为代码进行计算.因此,eval "ls" "-l" "file with space"eval ls -l file with space或完全相同eval "ls -l file with space".


最佳实践(无管道支持)

正如BashFAQ#50中给出的那样 - 以下运行其精确的参数列表作为简单命令的参数向量.

run_command() {
  "$@"
}
Run Code Online (Sandbox Code Playgroud)

这提供了许多保证:

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

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

run_pipeline() { foo "$@" | bar; }
run_command run_pipeline "argument one" "argument two"
Run Code Online (Sandbox Code Playgroud)

允许管道作为直接参数

要明确:我不建议使用此代码.通过免除|以下最佳做法提供的通常保护,它削弱了所述做法所提供的安全性.但是,它确实按照你的要求行事.

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

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


论为什么这是一个坏主意

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

echo '<hello>'
Run Code Online (Sandbox Code Playgroud)

这里,字符串中的<和已被引用,因此不再具有其原始行为.但是,一旦将这些值分配给数组或参数列表,就像在><hello>

args=( 'echo' '<hello>' )
Run Code Online (Sandbox Code Playgroud)

...不再存在关于引用或不引用哪些字符的元数据.从而,

echo hello '|' world
Run Code Online (Sandbox Code Playgroud)

变得完全无法区分

echo hello | world
Run Code Online (Sandbox Code Playgroud)

即使作为单独的命令,这些行为也会有不同的行为.


一个具体的例子

考虑以下:

run_command rm -rf -- "$tempdir" "$pidfile"
Run Code Online (Sandbox Code Playgroud)

在"最佳实践"示例中,无论这些值是什么,都可以保证将传递给的文件名tempdirpidfile文件名都视为文件名rm.

但是,使用"允许管道"示例,上面可以调用rm -rf -- | arbitrary-command-here,应该tempfile='|'pidfile=arbitrary-command-here.

为壳变量从该组当前的环境变量的初始化,和环境变量经常外部可控-这表现在用于远程攻击的存在弹震 -这不是一个纯理论的或空闲的关注.