何时将引号括在shell变量周围?


183

有人可以告诉我是否应该在Shell脚本中的变量周围加上引号吗?

例如,以下正确:

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

要么

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

如果是这样,为什么?



这个问题有很多重复项,其中很多都不是变量,所以我改名为“值”而不是“变量”。我希望这可以帮助更多的人找到这个话题。
三胞胎

1
@codeforester恢复的编辑怎么了?
Tripleee


Answers:


130

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

$?因为它是数字值,所以不需要引号。是否$URL需要它取决于您允许的内容以及是否为空(如果为空)。

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


2
注意,“空格”实际上意味着“任何空白”。
William Pursell

4
@Cristian:如果不确定变量中可能包含什么,则引用它更安全。我倾向于遵循与paxdiablo相同的原则,只是养成引用所有内容的习惯(除非有特定原因不这样做)。
Gordon Davisson '04年

11
如果您不知道IFS的价值,则无论如何都要引用它。如果为IFS=0,那么echo $?可能会非常令人惊讶。
Charles Duffy

3
根据上下文报价,而不是根据您期望的值报价,否则您的错误会变得更糟。例如,您确定所有路径都没有空格,因此您认为可以写cp $source1 $source2 $dest,但是如果由于某些意外原因dest未设置,第三个参数就会消失,并且它将静默复制source1过来,source2而不是给您一个对于空白目标的适当错误(如果用引号引起来的话会发生这种情况)。
Derek Veit '18

3
quote it if...思维过程向后-不需要时添加引号,而是在需要时将其删除。始终将字符串和脚本用单引号引起来,除非您需要使用双引号(例如,使变量扩展)或无需使用引号(例如,进行通配符和文件名扩展)。
Ed Morton

91

简而言之,在不需要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'

当您特别要求外壳执行令牌拆分和/或通配符扩展时,没有合适的引号。

代币分割

 $ 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元字符的值时,引号是可选的。因此,无引号$?基本上是可以的,因为该变量只能包含一个数字。但是,"$?"它也是正确的,建议使用它以确保总体一致性和正确性(尽管这是我个人的建议,而不是广泛认可的政策)。

不是变量的值基本上遵循相同的规则,尽管您也可以转义任何元字符而不用引用它们。对于一个常见的示例,&除非元字符被转义或引用,否则外壳中的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也可以很好地工作。但是,通配符匹配的循环是一个常见的问题,并且经常做不正确。)

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


1
这是我发布给相关问题的答案的一部分(部分)。我将其粘贴在这里,因为它简洁明了,定义明确,足以成为该特定问题的规范问题。
2014年

4
我将注意到,这是项#0,是mywiki.wooledge.org/BashPitfalls常见Bash错误集合上的重复出现的主题。该列表中的许多单独项目基本上都是关于此问题的。
三点

26

通常,这是一个三点式的引号公式:

双引号

在我们想抑制单词分裂和模糊的情况下。同样在我们希望将文字视为字符串而不是正则表达式的情况下。

单引号

在我们要抑制插值和反斜杠特殊处理的字符串文字中。换句话说,使用双引号的情况是不合适的。

无引号

在我们绝对确定没有分词或通配符问题的情况下,或者我们确实希望分词和通配符


例子

双引号

  • 带空格("StackOverflow rocks!""Steve's Apple")的文字字符串
  • 变量展开("$var""${arr[@]}"
  • 命令替换("$(ls)""`ls`"
  • 目录路径或文件名部分包含空格("/my dir/"*)的位置
  • 保护单引号("single'quote'delimited'string"
  • Bash参数扩展("${filename##*/}"

单引号

  • 其中包含空格的命令名称和参数
  • 需要抑制插值的文字字符串('Really costs $$!''just a backslash followed by a t: \t'
  • 保护双引号('The "crux"'
  • 需要抑制插值的正则表达式文字
  • 对包含特殊字符($'\n\t')的文字使用外壳引号
  • 在需要保护多个单引号和双引号($'{"table": "users", "where": "first_name"=\'Steve\'}')的地方使用外壳引号

无引号

  • 围绕标准数值变量($$$?$#等)
  • 在算术上下文喜欢((count++))"${arr[idx]}""${string:start:length}"
  • 内部[[ ]]表达式,没有分词和通配问题(这是样式问题,意见可能有很大不同)
  • 我们要分词的地方(for word in $words
  • 我们想在哪里浏览(for txtfile in *.txt; do ...
  • 我们想~被解释为$HOME~/"some dir"但不是"~/some dir")的地方

也可以看看:


3
根据这些准则,可以通过写以下内容来获得根目录中的文件列表"ls" "/" :短语“所有字符串上下文”需要更仔细地限定。
William Pursell '02

5
在中[[ ]],引号在=/ ===~:的右侧确实很重要,这使得将字符串解释为模式/正则表达式或按字面意义进行区别。
本杰明·

6
一个很好的概述,但是@BenjaminW。的注释值得集成,并且ANSI C引号的字符串($'...')肯定应该有自己的部分。
mklement0

3
@ mklement0,实际上它们是等效的。这些准则表明,您应该始终输入"ls" "/"而不是更常见的ls /,我认为这是准则中的主要缺陷。
William Pursell

4
如果没有引号,则可以添加变量赋值或case:)
PesaThe'Jan

4

"$var"为了安全起见,我通常使用引号之类的字体,除非我确定其中$var不包含空格。

我确实使用$var了一种加入行的简单方法:

lines="`cat multi-lines-text-file.txt`"
echo "$lines"                             ## multiple lines
echo $lines                               ## all spaces (including newlines) are zapped

最后的评论有些误导。换行符有效地替换为空格,而不是简单地删除。
Tripleee '18

-1

在shell脚本中使用变量时,请使用带引号的变量作为引号,这意味着该变量可能包含空格或特殊字符,不会影响您的shell脚本的执行。否则,如果您确定变量名中没有空格或特殊字符,则可以使用不带“”的空格或特殊字符。

例:

echo“ $ url name”-(可以一直使用)

echo“ $ url name”-(不能在这种情况下使用,因此在使用前要多加注意)

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.