我正在尝试使用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
函数不受空格,全局和任何其他特殊字符的影响?
答案 0 :(得分:6)
eval
将所有参数组合成一个字符串,并将该字符串计算为代码。因此,eval "ls" "-l" "file with space"
与eval ls -l file with space
或eval "ls -l file with space"
完全相同。
如BashFAQ #50中所述 - 以下运行其精确的参数列表作为简单命令的参数向量。
run_command() {
"$@"
}
这提供了许多保证:
>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"
在“最佳做法”示例中,保证将tempdir
和pidfile
的内容视为传递给rm
的文件名,无论这些值是什么。
但是,使用“允许管道”示例,上述内容可以调用rm -rf -- | arbitrary-command-here
,tempfile='|'
和pidfile=arbitrary-command-here
。
由于shell变量是从存在的环境变量集初始化的,而环境变量 通常是外部可控的 - 正如Shellshock的远程攻击的存在所证明的那样 - 这是不是纯粹的理论或空闲问题。