为啥 shell 忽略通过变量传递给它的参数中的引号字符? [复制]

Posted

技术标签:

【中文标题】为啥 shell 忽略通过变量传递给它的参数中的引号字符? [复制]【英文标题】:Why does shell ignore quoting characters in arguments passed to it through variables? [duplicate]为什么 shell 忽略通过变量传递给它的参数中的引号字符? [复制] 【发布时间】:2012-08-21 15:27:23 【问题描述】:

这些工作如宣传的那样:

grep -ir 'hello world' .
grep -ir hello\ world .

这些不:

argumentString1="-ir 'hello world'"
argumentString2="-ir hello\\ world"
grep $argumentString1 .
grep $argumentString2 .

尽管'hello world' 在第二个示例中被引号括起来,grep 将'hello(和hello\)解释为一个参数,将world'(和world)解释为另一个参数,这意味着,在这种情况下, 'hello 将是搜索模式,world' 将是搜索路径。

同样,这只发生在从argumentString 变量扩展参数时。在第一个示例中,grep 正确地将 'hello world'(和 hello\ world)解释为单个参数。

谁能解释这是为什么?是否有适当的方法来扩展字符串变量,以保留每个字符的语法,以便 shell 命令正确解释它?

【问题讨论】:

我应该注意这与 grep 本身没有任何关系;这更像是一个 bash 问题(使用任何其他命令具有相同的效果) 【参考方案1】:

当您将引号字符放入变量时,它们只是变成普通文字(参见http://mywiki.wooledge.org/BashFAQ/050;感谢@tripleee 指出此链接)

请尝试使用数组来传递参数:

argumentString=(-ir 'hello world')
grep "$argumentString[@]" .

【讨论】:

【参考方案2】:

为什么

当字符串被扩展时,它被分割成单词,但它不会被重新评估以找到特殊字符,例如引号或美元符号或......这就是 shell 一直以来的行为方式,因为Bourne shell 早在 1978 年左右。

修复

bash 中,使用数组来保存参数:

argumentArray=(-ir 'hello world')
grep "$argumentArray[@]" .

或者,如果勇敢/愚蠢,请使用eval

argumentString="-ir 'hello world'"
eval "grep $argumentString ."

另一方面,谨慎往往是勇敢的一部分,与eval 合作是谨慎胜于勇敢的地方。如果您不能完全控制 eval'd 字符串(如果命令字符串中有任何未经严格验证的用户输入),那么您将面临潜在的严重问题。

请注意,Bash 的扩展顺序在 GNU Bash 手册中的Shell Expansions 中进行了描述。请特别注意第 3.5.3 节 Shell 参数扩展、3.5.7 分词和 3.5.9 引号删除。

【讨论】:

或者,“不要那样做”。 mywiki.wooledge.org/BashFAQ/050 如果我们要用eval来展示,不妨用它来展示。我认为使用eval right always 包括将其传递给单个字符串-否则,您会将其所有参数与空格连接在一起,并且会以令人惊讶的方式变得混乱。考虑eval printf '%s\n' "hello world",与eval 'printf "%s\n" "hello world"' 相比,这是一个很好的例子,说明为什么传递eval 多个参数会导致混淆。 为什么grep "$argumentArray[@]" . 中的引号不会导致数组作为单个参数传递?我本来希望它是grep $argumentArray[@] .,但似乎两者都可以在实践中使用? @natevw:与"$@" 生成参数列表而不是单个字符串的原因大致相同("$*" 生成单个字符串,"$array[*]" 也是如此)。为什么"$@" 会这样做?因为自古以来就是这样(或者,至少是 Unix 的第 7 版,大约在 1978 年,它引入了 Bourne shell)。 @JonathanLeffler 我想要一个不限于引号的此答案的规范常见问题解答。我应该发布一个新问题吗?【参考方案3】:

在查看此问题和相关问题时,我很惊讶没有人提出使用显式子外壳。对于 bash 和其他现代 shell,您可以显式执行命令行。在 bash 中,它需要 -c 选项。

argumentString="-ir 'hello world'"
bash -c "grep $argumentString ."

完全按照原始提问者的要求工作。这种技术有两个限制:

    您只能在命令或参数字符串中使用单引号。 只有导出的环境变量可用于命令

此外,此技术处理重定向和管道,其他 shellisms 也可以工作。您还可以使用 bash 内部命令以及在命令行上工作的任何其他命令,因为您实际上是在要求 subshel​​l bash 将其直接解释为命令行。这是一个更复杂的例子,一个有点复杂的 ls -l 变体。

cmd="prefix=`pwd` && ls | xargs -n 1 echo \'In $prefix:\'"
bash -c "$cmd"

我已经用这种方式和参数数组构建了命令处理器。通常,这种方式更容易编写和调试,并且回显您正在执行的命令也很简单。 OTOH,当您确实有抽象的参数数组时,参数数组可以很好地工作,而不是只想要一个简单的命令变体。

【讨论】:

以上是关于为啥 shell 忽略通过变量传递给它的参数中的引号字符? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

Excel / VBA代码忽略输入的参数

4shell中的特殊变量

为啥这个 Ruby 方法通过引用传递它的参数

将变量传递给vagrant中的shell脚本配置器

给定传递给它的参数类型,如何确定函数参数的类型?

将变量传递给 vagrant 中的 shell 脚本配置器