在Bash中的$(命令替换)中报价


126

在我的Bash环境中,我使用包含空格的变量,并且在命令替换中使用这些变量。不幸的是我找不到关于SE的答案。

引用变量的正确方法是什么?如果将它们嵌套,该怎么办?

DIRNAME=$(dirname "$FILE")

还是我在替代词之外引用?

DIRNAME="$(dirname $FILE)"

或两者?

DIRNAME="$(dirname "$FILE")"

还是我会使用反引号?

DIRNAME=`dirname "$FILE"`

什么是正确的方法?我如何轻松检查报价是否设置正确?




1
这是一个很好的问题,但是考虑到所有与嵌入式毛坯有关的问题,为什么您会故意使用它们而使自己过得艰难?
2014年

3
@Joe,带空格的空白是指文件名中的空格吗?我个人并不经常使用它们,但是我正在与其他人的目录和文件一起工作,我不确定它们是否包含空格。此外,我认为最好立即将其改正,这样我以后就不必担心。
CousinCocaine 2014年

4
是。我们要和那些“其他人”做什么?<G>

Answers:


188

从最坏到最好:

  • DIRNAME="$(dirname $FILE)" 如果$FILE包含空格或通配符,将不会执行您想要的操作\[?*
  • DIRNAME=`dirname "$FILE"`在技​​术上是正确的,但是不建议在命令扩展中使用反引号,因为将它们嵌套时会增加额外的复杂性。
  • DIRNAME=$(dirname "$FILE")是正确的,但这仅仅是因为这是一个赋值。如果在任何其他上下文中使用命令替换,例如export DIRNAME=$(dirname "$FILE")du $(dirname "$FILE"),如果扩展结果包含空格或通配符,则缺少引号会引起麻烦。
  • DIRNAME="$(dirname "$FILE")"是推荐的方法。您可以DIRNAME=使用命令和空格替换而不更改任何其他内容,并dirname接收正确的字符串。

进一步改进:

  • DIRNAME="$(dirname -- "$FILE")"如果$FILE以破折号开头,则可以工作。
  • DIRNAME="$(dirname -- "$FILE"; printf x)" && DIRNAME="${DIRNAME%?x}"即使$FILE以换行符结尾也可以工作,因为$()在输出末尾截断换行符,并dirname在结果之后输出换行符。嘘dirname,你为什么要与众不同?

您可以随意嵌套命令扩展。随着$()你总是创建一个新的报价范围内,所以你可以做这样的事情:

foo "$(bar "$(baz "$(ban "bla")")")"

没有想尝试与反引号。


3
明确回答我的问题。当我嵌套这些变量时,是否可以像现在一样继续引用?
CousinCocaine 2014年

3
是否有参考/资源详细说明了引号内命令替换内引号的行为?
AmadeusDrZaius

12
@AmadeusDrZaius“随着$()您始终创建一个新的引用上下文”,因此就像在外部引号之外。据我所知,仅此而已。
l0b0 2015年

4
@ l0b0谢谢,是的,我发现您的解释很清楚。我只是想知道它是否也在某个地方的手册中。我确实在毛边发现了它(尽管是非正式的)。我想如果您仔细阅读有关替换顺序的信息,则可以得出此事实。
AmadeusDrZaius

1
因此嵌套引号是可以接受的,但是它们使我们望而却步,因为大多数语法着色方案都无法检测到特殊情况。整齐。
卢克·戴维斯

19

您始终可以使用来显示变量引用的效果printf

分词完成于var1

$ var1="hello     world"
$ printf '[%s]\n' $var1
[hello]
[world]

var1 引用,所以没有分词:

$ printf '[%s]\n' "$var1"
[hello     world]

var1内部单词拆分$(),等效于echo "hello" "world"

$ var2=$(echo $var1)
$ printf '[%s]\n' "$var2"
[hello world]

没有单词分裂var1,不引用没问题$()

$ var2=$(echo "$var1")
$ printf '[%s]\n' "$var2"
[hello     world]

单词var1再次分裂:

$ var2="$(echo $var1)"
$ printf '[%s]\n' "$var2"
[hello world]

引用两者,这是最简单的确定方法。

$ var2="$(echo "$var1")"
$ printf '[%s]\n' "$var2"
[hello     world]

通配问题

不引用变量也可能导致其内容的全局扩展:

$ mkdir test; cd test; touch file1 file2
$ var="*"
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]

请注意,这仅在变量展开后发生。赋值期间不必引用全局名称:

$ var=*
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]

使用set -f禁用此行为:

$ set -f
$ var=*
$ printf '[%s]\n' $var
[*]

set +f重新启用它:

$ set +f
$ printf '[%s]\n' $var
[file1]
[file2]

8
人们往往会忘记单词拆分不是唯一的问题,您可能想更改示例var1='hello * world'以说明问题。
斯特凡Chazelas

10

除了已接受的答案:

尽管我通常同意@ l0b0的回答,但我怀疑在“最坏到最好”列表中放置反引号至少部分是由于$(...)随处可见的这一假设的结果。我意识到这个问题指定了Bash,但是很多时候Bash都变成了/bin/sh,这实际上并不总是Bourne Again完整的外壳。

特别是,普通的Bourne shell将不知道如何处理$(...),因此声称与之兼容的脚本(例如,通过#!/bin/shshebang行)如果实际上是由“ real”运行的,则可能会出现错误的行为/bin/sh–这是例如,在生成初始脚本或打包前后脚本时特别感兴趣,并且可以在基础系统安装过程中将其放到令人惊讶的位置。

如果这听起来像是您打算使用此变量进行的操作,则嵌套可能比实际运行脚本要担心的要少。当这是一个简单的案例并且需要考虑可移植性时,即使我希望脚本通常/bin/shBash 所在的系统上运行,出于这个原因,我还是经常倾向于使用反引号,并使用多个分配而不是嵌套。

说了这么多,实现的无处不在的shell $(...)(Bash,Dash等)使我们处于一个很好的位置,可以在大多数情况下坚持使用更漂亮,更易于嵌套且最近更喜欢的POSIX语法, @ l0b0提及的所有原因。

顺便说一句:这也偶尔出现在StackOverflow上–


//,极好的答案。过去我也遇到过/ bin / sh的向后兼容性问题。您对如何使用向后兼容方法处理他的问题有任何建议吗?
内森·巴桑尼斯

以我的经验来看:有时您可能需要将反引号更改为用圆括号括起来的美元对数,而从来没有一次(具体来说是有必要)将用反引号括起来的美元对等货币。我只在JavaScript代码中写反引号,而在Shell代码中不写反引号。
史蒂文·卢
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.