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

Why does shell ignore quoting characters in arguments passed to it through variables? [duplicate]

提问人:Ben Wilhelm 提问时间:8/27/2012 最后编辑:oguz ismailBen Wilhelm 更新时间:7/27/2020 访问量:12869

问:

这些工作如广告所示:

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

这些不:

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

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

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

谁能解释一下为什么会这样?有没有一种合适的方法来扩展字符串变量,该变量将保留每个字符的语法,以便 shell 命令正确解释它?

bash 变量 语法 引用 扩展

评论

6赞 nneonneo 8/27/2012
我应该指出,这与 grep 本身没有任何关系;这更像是一个 bash 问题(使用任何其他命令都有相同的效果)
0赞 user1934428 11/8/2023
grep $argumentString1 .展开为 。grep -ir hello world .
0赞 user1934428 11/8/2023
grep $argumentString2 .扩展为(即反斜杠是 grep 的第二个参数的一部分)。grep -ir hello\ world .

答:

6赞 nneonneo 8/27/2012 #1

当你把引号字符放到变量中时,它们只是变成普通的文字(参见 http://mywiki.wooledge.org/BashFAQ/050;感谢@tripleee指出这个链接)

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

argumentString=(-ir 'hello world')
grep "${argumentString[@]}" .
50赞 Jonathan Leffler 8/27/2012 #2

为什么

当字符串展开时,它会被拆分为单词,但不会重新计算以查找特殊字符,例如引号或美元符号或...这就是自 1978 年左右的 Bourne shell 以来,shell 一直“一直”的行为方式。

修复

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

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

或者,如果勇敢/鲁莽,请使用:eval

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

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

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

评论

9赞 tripleee 8/27/2012
“或者,”那就不要那样做”。mywiki.wooledge.org/BashFAQ/050
2赞 Charles Duffy 3/29/2017
如果我们要展示使用 ,不妨展示正确使用它。我认为使用 right 总是包括向它传递一个字符串——否则,您将它的所有参数与空格连接在一起,这会以令人惊讶的方式变得混乱。考虑 ,与 相比,举例说明为什么传递多个参数会导致混淆。evalevaleval printf '%s\n' "hello world"eval 'printf "%s\n" "hello world"'eval
0赞 natevw 3/28/2018
为什么引号不会导致数组作为单个参数传递?我本来以为会是这样,但似乎两者在实践中都有效?grep "${argumentArray[@]}" .grep ${argumentArray[@]} .
2赞 Jonathan Leffler 3/28/2018
@natevw:大致相同的原因,即生成一个参数列表而不是单个字符串(并生成单个字符串,就像)。为什么要这样做?因为自古以来就是这样(或者至少是 Unix 第 7 版,大约 1978 年,它引入了 Bourne shell)。"$@""$*""${array[*]}""$@"
0赞 that other guy 8/5/2018
@JonathanLeffler 我想要一个不限于引号的答案的规范常见问题解答。我应该发布一个新问题吗?
4赞 markgo2k 9/28/2017 #3

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

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

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

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

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

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

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

评论

0赞 jrw32982 4/1/2022
命令中可以使用单引号和双引号。就像在命令行中键入时一样。cmd='x=" a "'\''$PATH'\''" b "'; echo "<$cmd>"; bash -c "$cmd; echo \"\$x\""
0赞 Charles Duffy 11/4/2022
“Subshell”,作为一个艺术术语,指的是由一个没有的 shell 创建的 shell——你可以得到它们从管道、命令替换、进程扩展和许多其他构造中隐式创建的。相比之下,当你运行时,这只是一个常规的子进程,恰好是一个 shell。fork()exec()bash -c '...'