这与宣传的一样:
# 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命令正确解释它?
答案 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 ."
完全像原始提问者一样工作。这种技术有两个限制:
此外,这种技术处理重定向和管道,其他shellism也可以。您还可以使用bash内部命令以及在命令行中运行的任何其他命令,因为您实际上是要求子shell bash将其直接解释为命令行。这是一个更复杂的例子,一个有点无偿的ls -l变体。
cmd="prefix=`pwd` && ls | xargs -n 1 echo \'In $prefix:\'"
bash -c "$cmd"
我已经通过这种方式和参数数组构建了命令处理器。通常,这种方式很多更容易编写和调试,并且回显您正在执行的命令是微不足道的。 OTOH,当你真的有抽象的参数数组时,param数组可以很好地工作,而不是只想要一个简单的命令变体。