我有一些 bash 脚本,其中一个包含以下内容:
#!/bin/bash
source $(dirname ${BASH_SOURCE[0]})/script.sh
而另一则内容如下:
#!/bin/bash
source "$(dirname ${BASH_SOURCE[0]})/script.sh"
这些脚本的行为有何不同以及为什么?什么是区别?
答案1
不带引号的字符串受分词和通配。也可以看看Bash 陷阱#14。
比较
$ echo $(printf 'foo\nbar\nquux\n*')
foo bar quux ssh-13yzvBMwVYgn ssh-3JIxkphQ07Ei ssh-6YC5dbnk1wOc
和
$ echo "$(printf 'foo\nbar\nquux\n*')"
foo
bar
quux
*
当发生分词时,第一个字符IFS
充当分隔符(默认情况下是空格)。
几乎在所有情况下您都需要添加引号。有一些例外,例如
在不发生分词/通配的表达式中,例如简单(非数组)赋值和语句
case
。以下都是安全的:foo=*
foo=${bar}qux${quux}
foo=$(bar "${quux}")
case ${var} in
然而,这不是(如果您所追求的是带有字面星号字符的单个元素):
foo=( * )
当您特别希望进行分词时,例如循环遍历以空格分隔的字符串中的标记(禁用通配符)。但是 - 如果可能,请使用数组。
答案2
主要区别在于引用的版本不受 shell 的字段分割的影响。
使用双引号,命令扩展的结果将作为source
命令的一个参数提供。如果没有引号,它将被分解为多个参数,具体取决于IFS
默认情况下包含空格、TAB 和换行符的值。
如果目录名称不包含此类空格,则不会发生字段拆分。
根据经验,最好在命令替换和变量扩展中使用双引号。
答案3
最重要的区别可能是脚本所在的目录中是否有空格。在这种情况下,第一行(没有双引号的行)将会失败。这将是 bash 对不带引号的字符串进行“分词”的结果。
假设 的结果dirname ${BASH_SOURCE[0]}
是/home/j r/bin
。考虑不带引号的行:
source $(dirname ${BASH_SOURCE[0]})/script.sh
在这种情况下,bash 将看到以下命令:
source /home/j r/bin/script.sh
分词后,该source
命令会看到脚本名称/home/j
和脚本参数r/bin/script.sh
。很可能没有该名称的脚本,bash 将返回错误消息:
bash: /bin/j: No such file or directory
现在考虑一下双引号会发生什么:
source "$(dirname ${BASH_SOURCE[0]})/script.sh"
在这种情况下,source 命令将查找名为的脚本/home/j r/bin/script.sh
并尝试获取它。
为了完整起见,让我们考虑单引号:
source '$(dirname ${BASH_SOURCE[0]})/script.sh'
在这种情况下,与前两种不同的是,dirname
它永远不会被执行。 source 命令将尝试使用文字 name 来获取命令$(dirname ${BASH_SOURCE[0]})/script.sh
。可能不存在这样的文件,bash 将发出错误消息。
bash 如何处理双引号中的字符串在以下内容中有详细描述man bash
:
Enclosing characters in double quotes preserves the literal value of all characters within the quotes, with the exception of $, `, \, and, when history expansion is enabled, !. The characters $ and ` retain their special meaning within double quotes. The backslash retains its special meaning only when followed by one of the following characters: $, `, ", \, or <newline>. A double quote may be quoted within double quotes by preceding it with a backslash. If enabled, history expansion will be performed unless an ! appearing in double quotes is escaped using a backslash. The backslash preceding the ! is not removed.