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

Ben*_*elm 33 variables syntax bash quoting expansion

这与宣传的一样:

# example 1
#!/bin/bash
grep -ir 'hello world' .
Run Code Online (Sandbox Code Playgroud)

这不是:

# example 2
#!/bin/bash
argumentString="-ir 'hello world'"
grep $argumentString .
Run Code Online (Sandbox Code Playgroud)

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

同样,这仅在从argumentString变量扩展参数时发生.grep 'hello world'在第一个示例中正确解释为单个参数.

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

Jon*_*ler 32

为什么

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

固定

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

argumentArray=(-ir 'hello world')
grep "${argumentArray[@]}" .
Run Code Online (Sandbox Code Playgroud)

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

argumentString="-ir 'hello world'"
eval "grep $argumentString ."
Run Code Online (Sandbox Code Playgroud)

另一方面,自由裁量权通常是勇敢的一部分,而与之合作的eval是一个自由裁量权优于勇敢的地方.如果您没有完全控制eval'd 的字符串(如果命令字符串中的任何用户输入未经过严格验证),那么您将面临潜在的严重问题.

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

  • 或者,"不要那样做".http://mywiki.wooledge.org/BashFAQ/050 (7认同)
  • 如果我们要展示使用`eval`,也可以展示正确使用它.我认为使用'eval` right*always*包括传递一个字符串 - 否则,你将所有的参数与空格连在一起,并且以令人惊讶的方式变得混乱.考虑`eval printf'%s \n'"hello world"`,与`eval'printf"%s \n""hello world"'`相比,为什么传递'eval`倍数的例子争论导致混乱. (2认同)
  • @natevw:大致与“$@”产生参数列表而不是单个字符串的原因相同(并且“$*”产生单个字符串,就像“${array[*]}”一样)。为什么 `"$@"` 会这样做?因为自古以来就是这样(或者至少是 1978 年左右的第七版 Unix,它引入了 Bourne shell)。 (2认同)

nne*_*neo 5

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

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

argumentString=(-ir 'hello world')
grep "${argumentString[@]}" .
Run Code Online (Sandbox Code Playgroud)


mar*_*o2k 5

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

argumentString="-ir 'hello world'"
bash -c "grep $argumentString ."
Run Code Online (Sandbox Code Playgroud)

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

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

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

cmd="prefix=`pwd` && ls | xargs -n 1 echo \'In $prefix:\'"
bash -c "$cmd"
Run Code Online (Sandbox Code Playgroud)

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