何时在shell变量周围包装引号?

Cri*_*ian 145 unix linux bash shell quotes

有人能告诉我是否应该在shell脚本中包含变量的引号?

例如,以下是正确的:

xdg-open $URL 
[ $? -eq 2 ]
Run Code Online (Sandbox Code Playgroud)

要么

xdg-open "$URL"
[ "$?" -eq "2" ]
Run Code Online (Sandbox Code Playgroud)

如果是这样,为什么?

pax*_*blo 102

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

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

我倾向于总是出于习惯而引用字符串,因为它更安全.

  • 如果你不知道IFS的价值,无论如何都要引用它.如果`IFS = 0`,那么`echo $?`可能会非常令人惊讶. (9认同)
  • `quote it if...` 有倒退的思维过程 - 引号不是您需要时添加的内容,而是您需要时删除的内容。除非您**需要**使用双引号(例如让变量扩展)或**需要**不使用引号(例如进行通配和文件名扩展),否则始终将字符串和脚本括在单引号中。 (7认同)
  • @Cristian:如果你不确定变量中可能包含什么,引用它会更安全.我倾向于遵循与paxdiablo相同的原则,只是养成引用一切的习惯(除非有特定的理由不这样做). (3认同)
  • 请注意,“空格”实际上意味着“任何空格”。 (2认同)
  • 根据上下文报价,而不是根据您期望的值报价,否则您的错误会更严重。例如,您确定所有路径都没有空格,因此您认为可以编写`cp $ source1 $ source2 $ dest`,但是如果由于某些意外原因未设置`dest`,则第三个参数将消失,它将以静默方式将`source1`复制到`source2`上,而不是为空白目标提供适当的错误(如引用每个参数时那样)。 (2认同)
  • 另外,有两种情况,引号不是必须的变量赋值`a=$b`和`[[` ... `]]`之间的测试,后面的双引号可以改变含义例如`a= *`, `[[ x = $a ]]` 成功而 `[[ x = "$a" ]]` 失败 (2认同)

tri*_*eee 77

简而言之,引用您不需要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.
Run Code Online (Sandbox Code Playgroud)

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

$ echo "There is no place like '$HOME'"
There is no place like '/home/me'
Run Code Online (Sandbox Code Playgroud)

当您明确要求shell执行令牌拆分和/或通配符扩展时,没有引号是合适的.

令牌分裂;

 $ words="foo bar baz"
 $ for word in $words; do
 >   echo "$word"
 > done
 foo
 bar
 baz
Run Code Online (Sandbox Code Playgroud)

相比之下:

 $ for word in "$words"; do echo "$word"; done
 foo bar baz
Run Code Online (Sandbox Code Playgroud)

(该循环仅在单个引用字符串上运行一次.)

 $ for word in '$words'; do echo "$word"; done
 $words
Run Code Online (Sandbox Code Playgroud)

(循环只运行一次,超过文字的单引号字符串.)

通配符扩展:

$ pattern='file*.txt'
$ ls $pattern
file1.txt      file_other.txt
Run Code Online (Sandbox Code Playgroud)

相比之下:

$ ls "$pattern"
ls: cannot access file*.txt: No such file or directory
Run Code Online (Sandbox Code Playgroud)

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

$ ls '$pattern'
ls: cannot access $pattern: No such file or directory
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

$ wget http://example.com/q&uack
[1] wget http://example.com/q
-bash: uack: command not found
Run Code Online (Sandbox Code Playgroud)

(当然,如果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
Run Code Online (Sandbox Code Playgroud)

最后一个例子也提出了另一个有用的概念,我喜欢称之为"跷跷板引用".如果您需要混合单引号和双引号,则可以将它们彼此相邻使用.例如,以下引用的字符串

'$HOME '
"isn't"
' where `<3'
"' is."
Run Code Online (Sandbox Code Playgroud)

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

$ echo '$HOME '"isn't"' where `<3'"' is."
$HOME isn't where `<3' is.
Run Code Online (Sandbox Code Playgroud)

这不是很清晰,但它是一种常见的技术,因此很有用.

另外,脚本通常不应该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
Run Code Online (Sandbox Code Playgroud)

(在后一个例子中,循环是完全多余的; printf特别适用于多个参数stat.但是,通过通配符匹配是一个常见问题,而且经常做错了.)

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

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

cod*_*ter 20

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

双引号

在我们想要抑制单词分裂和globbing的上下文中.同样在我们希望将文字视为字符串而不是正则表达式的上下文中.

单引号

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

没有报价

在我们完全确定没有单词分裂或全局问题的情况下,或者我们确实需要分词和通配.


例子

双引号

  • 带有空格的文字字符串("StackOverflow rocks!","Steve's Apple")
  • 变量扩展("$var","${arr[@]}")
  • 命令替换("$(ls)","`ls`")
  • 目录路径或文件名部分包含空格("/my dir/"*)的globs
  • 保护单引号("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)
  • 我们想要globbing(for txtfile in *.txt; do ...)
  • 我们希望~被解释为$HOME(~/"some dir"但不是"~/some dir")

也可以看看:

  • 一个很好的概述,但@ BenjaminW.的评论值得整合,ANSI C引用的字符串(`$'...'`)应该有自己的部分. (6认同)
  • 在`[[]]`中,引用在`=`/`==`和`= ~`的右侧确实很重要:它将字符串解释为模式/正则表达式或字面意思之间的区别. (5认同)
  • 根据这些指南,可以通过编写"ls""/"来获取根目录中的文件列表."所有字符串上下文"这一短语需要更加仔细地进行限定. (3认同)
  • @ mklement0,确实是等价的.这些指导原则表明你应该总是键入`"ls""/"`而不是更常见的`ls /`,我认为这是指南中的一个主要缺陷. (3认同)
  • 对于**没有引号**,您可以添加变量赋值或`case` :) (3认同)
  • 鉴于 `IFS` 管理分词,即使是标准的数字变量也应该被引用以获得最大程度的保护:`true; 回声 $?; IFS=0; 真的; x=$?; 回声 $x; 回声“$x”` (2认同)

Bac*_*ien 5

"$var"为了安全起见,我通常使用引号 like ,除非我确定它$var不包含空格。

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

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

  • 最后的评论有些误导;换行符实际上被空格替换,而不是简单地删除。 (2认同)