为什么shell忽略通过变量传递给它的参数中的引号?

时间:2012-08-27 06:09:20

标签: bash variables syntax quoting expansion

这与宣传的一样:

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

这不是:

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

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

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

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

3 个答案:

答案 0 :(得分:29)

为什么

当字符串被展开时,它被分成单词,但不会重新评估它以找到特殊字符,例如引号或美元符号或......这就是shell“始终”表现的方式,因为伯恩炮弹回到1978年左右。

修复

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

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

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

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

另一方面,自由裁量权往往是英勇的最佳部分,与eval合作是一个谨慎胜过勇敢的地方。如果您没有完全控制eval'的字符串(如果命令字符串中的任何用户输入未经过严格验证),那么您将面临潜在的严重问题。

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

答案 1 :(得分:5)

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

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

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

答案 2 :(得分:1)

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

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

完全像原始提问者一样工作。这种技术有两个限制:

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

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

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

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