关于Linux:什么时候用引号括住shell变量?

When to wrap quotes around a shell variable?

有人能告诉我是否应该在shell脚本中用引号括住变量吗?

例如,是否正确如下:

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

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

如果是这样,为什么?


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

$?不需要引号,因为它是一个数值。EDOCX1[1]是否需要它取决于你在其中允许什么,以及如果它是空的,你是否仍然需要一个论点。

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


简而言之,引用不需要shell执行令牌拆分和通配符扩展的所有内容。

单引号保护它们之间的文本。当您需要确保外壳完全不接触字符串时,它是正确的工具。通常,它是在不需要变量插值时选择的报价机制。

1
2
3
4
5
$ 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.

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

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

当您特别要求shell执行令牌拆分和/或通配符扩展时,不需要引号。

令牌拆分;

1
2
3
4
5
6
7
 $ words="foo bar baz"
 $ for word in $words; do
 >   echo"$word"
 > done
 foo
 bar
 baz

相比之下:

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

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

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

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

通配符扩展:

1
2
3
$ pattern='file*.txt'
$ ls $pattern
file1.txt      file_other.txt

相比之下:

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

(没有名为file*.txt的文件。)

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

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

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

当知道变量只能包含不包含shell元字符的值时,引用是可选的。因此,一个不带引号的$?基本上是可以的,因为这个变量只能包含一个数字。但是,"$?"也是正确的,并且建议总体一致性和正确性(尽管这是我个人的建议,而不是公认的政策)。

不是变量的值基本上遵循相同的规则,不过您也可以转义任何元字符而不是引用它们。对于一个常见的示例,shell将把包含&的URL作为后台命令进行解析,除非元字符被转义或引用:

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

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

1
2
3
4
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

最后一个例子还提出了另一个有用的概念,我喜欢称之为"seesaw-quoting"。如果需要将单引号和双引号混合使用,可以相邻使用。例如,以下带引号的字符串

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

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

1
2
$&nbsp;echo '$HOME '"isn't"' where `<3'"' is."
$HOME isn't where `<3' is.

这不是非常清晰的,但这是一种常见的技巧,因此很容易知道。

另一方面,脚本通常不应为任何内容使用ls。要扩展通配符,只需…用它。

1
2
3
4
5
6
7
8
9
10
11
$ printf '%s
'
$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
'
"$file"
> done
Found file: file1.txt
Found file: file_other.txt

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

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


以下是报价的三点公式:

双引号

在我们想要禁止分词和全局搜索的上下文中。另外,在我们希望将文本视为字符串而不是正则表达式的上下文中。

单引号

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

无引号

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

实例

双引号

  • 带空白的文本字符串("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"')
  • 需要抑制插值的regex文本
  • 对涉及特殊字符的文字使用外壳引用($'
    \t'
    )
  • 在需要保护多个单引号和双引号的地方使用shell引号($'{"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")。

参见:

  • bash中单引号和双引号的区别
  • 什么是特殊的美元符号外壳变量?
  • 引述和转义-bash hacker'wiki
  • 什么时候需要双报价?


我通常使用像"$var"这样的引用来保证安全,除非我确信$var不包含空格。

我确实使用$var作为连接线条的简单方法:

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


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

例子:

echo"$url name"--(可随时使用)

echo"$url name"--(在这种情况下不能使用,因此在使用之前要小心)