42

这些工作如广告:

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

这些不会:

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

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

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

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

4

3 回答 3

45

为什么

当字符串被扩展时,它被拆分成单词,但不会重新评估它以找到特殊字符,例如引号或美元符号或......这是 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 引用删除。

于 2012-08-27T06:19:30.657 回答
6

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

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

argumentString=(-ir 'hello world')
grep "${argumentString[@]}" .
于 2012-08-27T06:18:22.773 回答
5

在查看此问题和相关问题时,我很惊讶没有人提出使用显式子shell。对于 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,当您确实拥有抽象的参数数组时,参数数组可以很好地工作,而不仅仅是想要一个简单的命令变体。

于 2017-09-27T23:02:35.827 回答