在bash中,当参数作为variables传递给它们时,为什么shell命令在参数中忽略引号?

这工作如广告:

# example 1 #!/bin/bash grep -ir 'hello world' . 

这不:

 # example 2 #!/bin/bash argumentString="-ir 'hello world'" grep $argumentString . 

尽pipe在第二个例子中引用了'hello world' ,但是grep将'hello as one argument and world'为另一个,这意味着在这种情况下, 'hello将是search模式, world'将成为searchpath。

同样,只有当参数从argumentStringvariables扩展时才会发生这种情况。 在第一个例子中,grep正确地将'hello world'为单个参数。

任何人都可以解释为什么这是? 有没有适当的方法来扩展一个stringvariables,将保留每个字符的语法,使其正确解释的shell命令?

为什么

当string被扩展时,它被分割成单词,但是它不会被重新评估以find特殊字符,例如引号或美元符号或…这就是shell自始至终performance出来的方式,因为Bourne shell返回在1978年左右。

固定

bash ,使用一个数组来保存参数:

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

或者,如果勇敢/蛮干,请使用eval

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

另一方面,谨慎往往是勇气的更好的一部分,与eval一起工作是一个谨慎胜于勇敢的地方。 如果您不完全控制eval的string(如果在命令string中有任何用户input未经过严格validation),那么您将面临潜在的严重问题。

请注意,Bash的扩展序列在GNU Bash手册的Shell Expansions中有描述。 请特别注意章节3.5.3shell参数扩展,3.5.7分词和3.5.9报价清除。

当把引号字符放入variables中时,它们只是简单的文字(参见http://mywiki.wooledge.org/BashFAQ/050 ;感谢@tripleee指出这个链接)

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

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

在双引号内,单引号失去了特殊的含义,所以它们只是普通的字符。 为了工作,使用像eval这样的东西:

 eval grep $argumentString . 

这应该从串联的string中正确地重新组装命令行。

在看这个和相关的问题,我很惊讶,没有人提出使用明确的子shell。 对于bash和其他现代的shell,你可以明确地执行命令行。 在bash中,它需要-c选项。

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

与所需的原始提问者完全一致。 这种技术有两个限制:

  1. 您只能在命令或参数string中使用单引号。
  2. 只有导出的环境variables可用于该命令

此外,这种技术处理redirect和pipe道,其他shellisms也起作用。 你也可以使用bash内部命令以及命令行中的任何其他命令,因为实质上是要求子shell bash将其直接解释为命令行。 这是一个更复杂的例子,一个有点无理复杂的ls -l变体。

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

我已经用这种方法和参数数组构build了命令处理器。 一般来说,这种方式编写和debugging起来容易得多 ,而且回显你正在执行的命令也是微不足道的。 OTOH,当你确实拥有抽象的参数数组时,param数组可以很好地工作,而不是仅仅需要一个简单的命令variables。