我啥时候应该在 shell 变量周围加上引号?
Posted
技术标签:
【中文标题】我啥时候应该在 shell 变量周围加上引号?【英文标题】:When should I wrap quotes around a shell variable?我什么时候应该在 shell 变量周围加上引号? 【发布时间】:2022-01-19 08:59:07 【问题描述】:我应该还是不应该在 shell 脚本中为变量加上引号?
例如,以下是否正确:
xdg-open $URL
[ $? -eq 2 ]
或
xdg-open "$URL"
[ "$?" -eq "2" ]
如果是这样,为什么?
【问题讨论】:
另见unix.stackexchange.com/questions/171346/… 这个问题有很多重复项,其中很多不是关于变量的,所以我将标题改为“值”而不是“变量”。我希望这可以帮助更多人找到这个主题。 @codeforester 恢复的编辑是怎么回事? 相关:Difference between single and double quotes in Bash 也是。 Bash 是一种 hack,其最终用途远远超出其设计考虑的范围。有更好的做事方式,但没有“正确/安全的方式”。我这样说是因为这里有很多参考资料都会有相反的意见,而且对于那些习惯于为特定任务设计的新语言和工具的人来说,这可能会变得非常混乱。 【参考方案1】:一般规则:如果它可以为空或包含空格(或任何空格)或特殊字符(通配符),则引用它。不使用空格引用字符串通常会导致 shell 将单个参数分解为多个参数。
$?
不需要引号,因为它是一个数值。 $URL
是否需要它取决于您在其中允许的内容以及是否仍需要参数(如果它为空)。
出于习惯,我倾向于总是引用字符串,因为这样更安全。
【讨论】:
请注意,“空格”实际上是指“任何空白”。 @Cristian:如果您不确定变量中可能包含什么,那么引用它会更安全。我倾向于遵循与 paxdiablo 相同的原则,只是养成引用所有内容的习惯(除非有特定的理由不这样做)。 如果你不知道IFS的值,无论如何都要引用它。如果IFS=0
,那么echo $?
可能会非常令人惊讶。
根据上下文引用,而不是你期望的值,否则你的错误会更糟。例如,你确定你的路径中没有空格,所以你认为你可以写cp $source1 $source2 $dest
,但是如果因为一些意想不到的原因没有设置dest
,第三个参数就消失了,它会默默地复制source1
超过 source2
而不是为空白目的地提供适当的错误(如果您引用了每个参数就会出现这种错误)。
quote it if...
的思维过程倒退了——引号不是你需要时添加的东西,而是你需要时删除的东西。除非您需要使用双引号(例如让变量扩展)或需要不使用引号(例如进行通配和文件操作,否则请始终将字符串和脚本用单引号括起来名称扩展)。【参考方案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 执行分词和/或通配符扩展时,不适合使用引号。
Word splitting(又名令牌拆分);
$ 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.
这不是很清楚,但它是一种常见的技术,因此很高兴知道。
顺便说一句,脚本should usually not use ls
for anything. 要扩展通配符,只需...使用它。
$ 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
也是。但是循环通配符匹配是一个常见问题,并且经常不正确。)
包含要循环的标记列表或要扩展的通配符的变量不太常见,因此我们有时缩写为“引用所有内容,除非您确切知道自己在做什么”。
【讨论】:
这是我发布到related question 的答案的变体(部分)。我在这里粘贴它是因为它简洁且定义明确,足以成为这个特定问题的规范问题。 我会注意到这是第 0 项,并且是 mywiki.wooledge.org/BashPitfalls 常见 Bash 错误集合上的一个反复出现的主题。该列表中的许多许多单独的项目基本上都是关于这个问题的。 令牌拆分在 Bash 参考中称为分词。请参阅我的编辑。 gnu.org/software/bash/manual/html_node/Word-Splitting.html @Roland 谢谢!我改写了文本以更喜欢官方术语。【参考方案3】:这里有一个三点式的报价公式:
双引号
在我们想要抑制分词和通配符的上下文中。同样在我们希望将文字视为字符串而不是正则表达式的上下文中。
单引号
在我们想要抑制插值和反斜杠特殊处理的字符串文字中。换句话说,不适合使用双引号的情况。
无引号
在我们绝对确定不存在分词或通配问题或我们确实需要分词和通配的上下文中。
示例
双引号
带空格的文字字符串("*** rocks!"
、"Steve's Apple"
)
变量扩展("$var"
、"$arr[@]"
)
命令替换("$(ls)"
、"`ls`"
)
目录路径或文件名部分包含空格的 glob ("/my dir/"*
)
保护单引号 ("single'quote'delimited'string"
)
Bash 参数扩展 ("$filename##*/"
)
单引号
命令名称和参数中包含空格 需要抑制插值的文字字符串('Really costs $$!'
、'just a backslash followed by a t: \t'
)
保护双引号 ('The "crux"'
)
需要抑制插值的正则表达式文字
对涉及特殊字符的文字使用 shell 引用 ($'\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"
)
另见:
Difference between single and double quotes in Bash What are the special dollar sign shell variables? Quotes and escaping - Bash Hackers' Wiki When is double quoting necessary?【讨论】:
根据这些指南,可以通过编写"ls" "/"
来获取根目录中的文件列表,“所有字符串上下文”这个短语需要更仔细地限定。
在[[ ]]
中,引用在=
/==
和=~
的右侧确实很重要:它在将字符串解释为模式/正则表达式或字面意思之间有所不同。
一个很好的概述,但@BenjaminW. 的 cmets 值得集成,ANSI C 引用的字符串 ($'...'
) 肯定应该有自己的部分。
@mklement0,确实它们是等价的。这些准则表明您应该始终键入"ls" "/"
而不是更常见的ls /
,我认为这是准则中的一个主要缺陷。
对于无引号,您可以添加变量赋值或case
:)【参考方案4】:
为了安全起见,我通常使用 "$var"
之类的引号,除非我确定 $var
不包含空格。
我确实使用$var
作为连接线的简单方法:
lines="`cat multi-lines-text-file.txt`"
echo "$lines" ## multiple lines
echo $lines ## all spaces (including newlines) are zapped
【讨论】:
最后的评论有些误导;换行符被有效地替换为空格,而不是简单地删除。【参考方案5】:为了在shell脚本中使用变量,使用“”引用变量作为引用变量意味着变量可能包含不会影响shell脚本执行的空格或特殊字符。否则,如果您确定变量名中没有任何空格或特殊字符,那么您可以在没有“”的情况下使用它们。
例子:
echo "$url name" # (Can be used at all times)
echo "$url name" # (Cannot be used at such situations so take precaution before using it)
【讨论】:
为什么这个答案会有删除投票?这个答案有什么问题? @PeterMortensen 它没有添加任何新信息。现在进行第二次删除投票。【参考方案6】:每当您的编辑器的https://www.shellcheck.net/ 插件告诉您时。
【讨论】:
以上是关于我啥时候应该在 shell 变量周围加上引号?的主要内容,如果未能解决你的问题,请参考以下文章