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

这些工作如宣传的那样:

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 问题(使用任何其他命令具有相同的效果)

标签: bash variables syntax quoting expansion


【解决方案1】:

为什么

当字符串被扩展时,它被分割成单词,但它不会被重新评估以找到特殊字符,例如引号或美元符号或......这就是 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 节外壳参数扩展、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 我想要一个不限于引号的此答案的规范常见问题解答。我应该发布一个新问题吗?
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

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

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

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

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

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

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

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

    【讨论】:

      最近更新 更多