我什么时候应该在 shell 变量周围加上引号?

新手上路,请多包涵

我应该还是不应该在 shell 脚本中为变量加上引号?

例如,以下是否正确:

 xdg-open $URL
[ $? -eq 2 ]

或者

xdg-open "$URL"
[ "$?" -eq "2" ]

如果是这样,为什么?

原文由 Cristian 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 688
2 个回答

一般规则:如果它可以为空或包含空格(或任何空格)或特殊字符(通配符),请引用它。不使用空格引用字符串通常会导致 shell 将单个参数分解为多个参数。

$? 不需要引号,因为它是一个数值。 $URL 是否需要它取决于你在那里允许的内容以及如果它是空的你是否仍然需要一个参数。

我倾向于总是出于习惯引用字符串,因为这样更安全。

原文由 paxdiablo 发布,翻译遵循 CC BY-SA 3.0 许可协议

简而言之,在不需要 shell 执行分词和通配符扩展的地方引用所有内容。

单引号逐字保护它们之间的文本。当您需要确保外壳完全不接触琴弦时,它是合适的工具。通常,当您不需要变量插值时,它是首选的引用机制。

 $ echo 'Nothing \t in here $will change'
Nothing \t in here $will change

$ grep -F '@&$*!!' file /dev/null
file:I can't get this @&$*!! quoting right.

当需要变量插值时,双引号是合适的。通过适当的调整,当您需要在字符串中使用单引号时,它也是一个很好的解决方法。 (没有直接的方法可以在单引号之间转义单引号,因为单引号内没有转义机制——如果有,它们不会完全逐字引用。)

 $ echo "There is no place like '$HOME'"
There is no place like '/home/me'

当您特别需要 shell 执行分词和/或通配符扩展时,不适合使用引号。

分词(又名分词);

  $ words="foo bar baz"
 $ for word in $words; do
 >   echo "$word"
 > done
 foo
 bar
 baz

相比之下:

  $ for word in "$words"; do echo "$word"; done
 foo bar baz

(循环仅在单个带引号的字符串上运行一次。)

  $ for word in '$words'; do echo "$word"; done
 $words

(循环仅在文字单引号字符串上运行一次。)

通配符扩展:

 $ pattern='file*.txt'
$ ls $pattern
file1.txt      file_other.txt

相比之下:

 $ ls "$pattern"
ls: cannot access file*.txt: No such file or directory

(没有按字面命名的文件 file*.txt 。)

 $ ls '$pattern'
ls: cannot access $pattern: No such file or directory

(也没有名为 $pattern 的文件!)

更具体地说,任何包含文件名的内容通常都应该被引用(因为文件名可以包含空格和其他 shell 元字符)。通常应该引用包含 URL 的任何内容(因为许多 URL 包含 shell 元字符,如 ?& )。通常应引用任何包含正则表达式的内容(同上)。需要引用除非空白字符之间的单个空格之外的任何包含重要空格的内容(否则,shell 会将空格有效地转换为单个空格,并修剪任何前导或尾随空格)。

当您知道一个变量只能包含一个不包含 shell 元字符的值时,引用是可选的。因此,不带引号的 $? 基本上没问题,因为这个变量只能包含一个数字。然而, "$?" 也是正确的,并且推荐用于一般的一致性和正确性(尽管这是我个人的建议,而不是广泛认可的政策)。

不是变量的值基本上遵循相同的规则,尽管您也可以转义任何元字符而不是引用它们。举一个常见的例子,一个带有 & 的 URL 将被 shell 解析为后台命令,除非元字符被转义或引用:

 $ wget http://example.com/q&uack
[1] wget http://example.com/q
-bash: uack: command not found

(当然,如果 URL 位于未引用的变量中,也会发生这种情况。)对于静态字符串,单引号最有意义,尽管此处可以使用任何形式的引用或转义。

 wget 'http://example.com/q&uack'  # Single quotes preferred for a static string
wget "http://example.com/q&uack"  # Double quotes work here, too (no $ or ` in the value)
wget http://example.com/q\&uack   # Backslash escape
wget http://example.com/q'&'uack  # Only the metacharacter really needs quoting

最后一个例子还暗示了另一个有用的概念,我喜欢称之为“跷跷板报价”。如果需要混合使用单引号和双引号,可以相邻使用。例如,以下引用的字符串

'$HOME '
"isn't"
' where `<3'
"' is."

可以背靠背粘贴在一起,在标记化和引号删除后形成一个长字符串。

 $ echo '$HOME '"isn't"' where `<3'"' is."
$HOME isn't where `<3' is.

这不是很清晰,但它是一种常见的技术,因此很高兴知道。

顺便说一句,脚本 通常不应该使用 ls 做任何事情。 要扩展通配符,只需…使用它。

 $ printf '%s\n' $pattern   # not ``ls -1 $pattern''
file1.txt
file_other.txt

$ for file in $pattern; do  # definitely, definitely not ``for file in $(ls $pattern)''
>  printf 'Found file: %s\n' "$file"
> done
Found file: file1.txt
Found file: file_other.txt

(在后一个示例中,循环是完全多余的; printf 特别适用于多个参数 stat 也是。但是循环通配符匹配是一个常见问题,并且经常不正确。 )

包含要循环的标记列表或要扩展的通配符的变量不太常见,因此我们有时缩写为“引用所有内容,除非您确切知道自己在做什么”。

原文由 tripleee 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题