保护包含空格的参数免受eval函数的影响

4
为了让包含参数内部空格的命令正常运行,我目前只发现以下方法可行:
eval 'sed 's/foo/foo'" "'bar/g' filename'

在一个假设的程序中,用户将输入一个命令,然后是要传递给eval的命令和参数,这不是一个非常优雅或健壮的解决方案。是否有其他方法来运行eval命令,以便my_command的接口可以更加用户友好?以下是程序现在接受参数的示例。

my_command 'sed 's/foo/foo'" "'bar/g' filename'

我希望界面能够像这样工作:
my_command sed 's/foo/foo bar/g' filename

编辑:

我会尝试问一个不同的问题:

如何让bash从命令行直接读取输入?我希望保留精确的输入,所以如果有引号,我想保留它们。我可以通过使用egrep从文件中读取然后净化输入来完成我的目标,如下所示:

egrep '/.*/' filename |
sed 's/\(.*\)['"'"']\(.*\) \(.*\)['"'"']\(.*\)/\1'"\'"'\2" "\3'"\'"'\4/g'

使用 "filename" 包含这一行
sed 's/foo/foo bar/g' file

这将为我提供所需的输出:
sed 's/foo/foo" "bar/g' file

这里的问题在于我无法直接使用echo "$@",因为bash会解释引号。我希望能够直接获取输入而不用从文件中读取。
4个回答

6

原始问题

对于您的首选用例,您只需在my_command内编写:

针对您的首选用例,您只需在my_command中写入以下内容:

"$@"

执行给定的命令。

您的eval行很奇怪:

eval 'sed 's/foo/foo'" "'bar/g' filename'

由于单引号不能嵌套,因此它等同于:

 eval 'sed s/foo/foo" "bar/g filename'

修订后的问题

可能的解决方案:

egrep '/.*/' filename | sh

这将直接将filename中的内容馈送给shell进行解释。假设有file包含以下内容:

Some text containing foo; and bar.
More foo bar?
More text; more foo and bar; more foo bar beyond the possibility of unfooing.

输出结果为:
Some text containing foo bar; and bar.
More foo bar bar?
More text; more foo bar and bar; more foo bar bar beyond the possibility of unfoo baring.

修复引号很难!

请注意,您的复杂的sed脚本还不够复杂。假设有一个包含以下内容的filename文件:

sed 's/foo/foo bar/g' file
sed 's/foo bar/foo bar baz/g' file

来自以下输出:

egrep '/.*/' filename |
sed 's/\(.*\)['"'"']\(.*\) \(.*\)['"'"']\(.*\)/\1'"\'"'\2" "\3'"\'"'\4/g'

is:

sed 's/foo/foo" "bar/g' file
sed 's/foo bar/foo bar" "baz/g' file

这并没有解决eval的所有问题。

我花了很多时间,断断续续地在这些问题上工作了相当长的一段时间(一个季度丝毫不夸张),这并不是微不足道的。你可以在如何在bash脚本中迭代参数中找到一次详细的讨论。我还有另一个答案,它通过这些东西进行了旋转,但我不能立即找到它(“立即”意味着分心搜索了大约一个小时,其中分心是重复的问题集等)。它可能已经被删除,或者我可能在错误的地方找到了它。


谢谢!我解决了嵌套引号问题,也更具体地提出了问题。 - Jon Poler
感谢您在这个回复中所花费的时间!我已经准备好相信你对这个问题的复杂性的判断,并探索其他替代方案,例如命令选项,或者像某人在其他地方建议的那样,使用 ANSI C 引用并直接在命令参数中输入所需文本。 - Jon Poler

1

数组引用

以下方法通过对数组的每个元素进行引用来保留参数中的空格:

function token_quote {
  local quoted=()
  for token; do
    quoted+=( "$(printf '%q' "$token")" )
  done
  printf '%s\n' "${quoted[*]}"
}

例子用法:
$ token_quote token 'single token' token
token single\ token token

请注意,上面的单个标记的空格被引用为\
$ set $(token_quote token 'single token' token)
$ eval printf '%s\\n' "$@"
token
single token
token
$

这表明标记确实被保持分开。
给定一些不受信任的用户输入:
% input="Trying to hack you; date"

构建一个执行 eval 的命令:
% cmd=(echo "User gave:" "$input")

使用看似正确的引用对其进行评估:

% eval "$(echo "${cmd[@]}")"
User gave: Trying to hack you
Thu Sep 27 20:41:31 +07 2018

请注意,您被黑客攻击了。 date 被执行而不是直接输出。

使用 token_quote() 替代:

% eval "$(token_quote "${cmd[@]}")"
User gave: Trying to hack you; date
%

eval 不是邪恶的,只是被误解了 :)


1
你的设计有缺陷。创建一个用户界面,使用户不能直接输入命令。提供选项或只允许它们输入参数。 在后端,在调用sed或其他所需工具之前,对参数进行净化检查。不必使用eval。

0

它实际上可以按照您的要求工作。使用"$@" - 这将完全传递命令行上给定的所有参数。

如果my_command.sh包含:

sed "$@"

然后,my_command.sh 's/foo/foo bar/g' filename将会按照你的期望执行。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接