何时将引号括起来?

When to wrap quotes around a shell variable?

提问人:Cristian 提问时间:4/9/2012 最后编辑:codeforesterCristian 更新时间:6/14/2023 访问量:156343

问:

我是否应该或不应该在 shell 脚本中的变量周围使用引号?

例如,以下内容是否正确:

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

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

如果是这样,为什么?

Linux Bash Shell Unix 行情

评论

3赞 tripleee 12/23/2015
另请参阅 unix.stackexchange.com/questions/171346/...
1赞 tripleee 3/28/2017
这个问题有很多重复,其中许多与变量无关,所以我将标题改为“值”而不是“变量”。我希望这能帮助更多的人找到这个话题。
1赞 tripleee 4/12/2017
@codeforester 恢复的编辑是怎么回事?
2赞 codeforester 9/14/2018
相关新闻: Bash 中单引号和双引号之间的区别
6赞 Tegra Detra 4/21/2019
Bash 是一种黑客攻击,最终的使用远远超出了其设计考虑的范围。有更好的做事方法,但没有“正确/安全的方法”。我之所以这样说,是因为这里有很多参考资料,它们都会有相反的意见,这可能会变得非常混乱,特别是对于那些习惯于为特定任务设计的新语言和工具的人来说。

答:

246赞 paxdiablo 4/9/2012 #1

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

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

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

评论

5赞 William Pursell 4/9/2012
请注意,“空格”实际上意味着“任何空格”。
6赞 Gordon Davisson 4/10/2012
@Cristian:如果您不确定变量中可能包含什么,引用它更安全。我倾向于遵循与paxdiablo相同的原则,只是养成引用所有内容的习惯(除非有特定原因不这样做)。
22赞 Charles Duffy 2/8/2017
如果您不知道 IFS 的价值,无论如何都要引用它。如果 ,那么可能会非常令人惊讶。IFS=0echo $?
10赞 Derek Veit 1/25/2018
根据上下文进行引用,而不是根据你期望的值来引用,否则你的错误会更糟。例如,你确定你的路径都没有空格,所以你认为你可以写 ,但是如果由于某种意想不到的原因没有被设置,第三个参数就会消失,它会静默地复制,而不是给你一个适当的空白目标错误(就像你引用了每个参数一样)。cp $source1 $source2 $destdestsource1source2
18赞 Ed Morton 4/19/2019
quote it if...有倒退的思维过程 - 引号不是你需要添加的东西,而是你需要删除的东西。始终将字符串和脚本括在单引号中,除非您需要使用双引号(例如,让变量扩展)或不需要使用引号(例如,进行通配和文件名扩展)。
167赞 tripleee 12/30/2014 #2

简而言之,引用不需要 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 元字符的值时,引号是可选的。因此,不带引号的变量基本上没问题,因为这个变量只能包含一个数字。但是,也是正确的,并建议保持一般的一致性和正确性(尽管这是我个人的建议,而不是广泛认可的政策)。$?"$?"

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

$ 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

(在后一个示例中,循环是完全多余的; 具体适用于多个参数。 太。但是遍历通配符匹配是一个常见问题,而且经常做错了。printfstat

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

评论

1赞 tripleee 12/30/2014
这是我发布的相关问题答案的(部分)变体。我把它粘贴到这里,因为它简洁明了,定义明确,足以成为这个特定问题的规范问题。
5赞 tripleee 1/28/2017
我会注意到这是项目 #0,也是 mywiki.wooledge.org/BashPitfalls 个常见 Bash 错误集合中反复出现的主题。该清单上的许多项目基本上都是关于这个问题的。
0赞 Roland 11/9/2021
令牌拆分在 Bash 引用中称为单词拆分。请看我的编辑。 gnu.org/software/bash/manual/html_node/Word-Splitting.html
1赞 tripleee 11/9/2021
@Roland谢谢!我改写了文本,更喜欢官方术语。
0赞 hanshenrik 5/18/2022
null 字节应该作为狗屎获得荣誉奖,即使它们不应该改变,也会改变,例如:+ 将打印 0...NULL="$(php -r 'echo chr(0);')"printf "%s" "$NULL" | wc -c
50赞 codeforester 2/8/2017 #3

以下是一般报价的三点公式:

双引号

在我们想要抑制单词拆分和通配的上下文中。此外,在我们希望将文字视为字符串而不是正则表达式的上下文中。

单引号

在字符串文字中,我们希望抑制插值和对反斜杠的特殊处理。换言之,使用双引号是不合适的情况。

无引号

在我们绝对确定不存在单词拆分或通配问题的上下文中,或者我们确实想要单词拆分和通配


例子

双引号

  • 带有空格 (,"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')
  • 在我们需要保护多个单引号和双引号的地方使用 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")

另请参阅:

评论

3赞 William Pursell 2/11/2017
根据这些准则,可以通过编写短语“所有字符串上下文”来获得根目录中的文件列表,需要更仔细地限定。"ls" "/"
7赞 Benjamin W. 3/26/2017
在 中,引用在 / 和 : 的右侧确实很重要,它使将字符串解释为模式/正则表达式还是字面意思之间的区别。[[ ]]====~
7赞 mklement0 6/11/2017
一个很好的概述,但 @BenjaminW. 的评论值得整合,ANSI C 引号字符串 () 绝对应该有自己的部分。$'...'
3赞 William Pursell 6/12/2017
@mklement0,它们确实是等价的。这些准则表明你应该总是输入而不是更常见的,我认为这是准则的一个主要缺陷。"ls" "/"ls /
5赞 PesaThe 1/7/2019
如果没有引号,您可以添加变量赋值或:)case
6赞 Bach Lien 1/17/2018 #4

我通常使用引号 like 来表示安全,除非我确定它不包含空格。"$var"$var

我确实使用一种简单的方法来连接行:$var

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

评论

2赞 tripleee 10/10/2018
最后的评论有些误导;换行符被有效地替换为空格,而不是简单地删除。
0赞 bobbogo 5/11/2023
如果多行文本文件.txt包含单词怎么办?bash 会将其替换为当前目录中所有文件的列表。哈哈。不是哈哈*
0赞 Bach Lien 5/13/2023
是的,这只是一个简单的方法,而不是一个确定的方法