如何在“$0”和“${BASH_SOURCE[0]}”之间进行选择

GNU的描述对我帮助不大。

    BASH_SOURCE
    
 An array variable whose members are the source filenames where the
 corresponding shell function names in the FUNCNAME array variable are
 defined. The shell function ${FUNCNAME[$i]} is defined in the file
 ${BASH_SOURCE[$i]} and called from ${BASH_SOURCE[$i+1]}

当前回答

我建议使用${BASH_SOURCE:-$0}作为最通用的变体。

之前的答案很好,但他们没有提到直接使用${BASH_SOURCE[0]}的一个警告:如果你调用脚本作为sh的参数,并且你的sh不是bash的alias(在我的情况下,在Ubuntu 16.04.5 LTS上,它链接到dash), BASH_SOURCE变量为空/未定义可能会失败。这里有一个例子:

t.sh:

#!/usr/bin/env bash

echo "\$0:                     [$0]"
echo "\$BASH_SOURCE:           [$BASH_SOURCE]"
echo "\$BASH_SOURCE or \$0:    [${BASH_SOURCE:-$0}]"
echo "\$BASH_SOURCE[0] or \$0: [${BASH_SOURCE[0]:-$0}]"

(成功地)运行:

$ ./t.sh
$0:                    [./t.sh]
$BASH_SOURCE:          [./t.sh]
$BASH_SOURCE or $0:    [./t.sh]
$BASH_SOURCE[0] or $0: [./t.sh]

$ source ./t.sh
$0:                    [/bin/bash]
$BASH_SOURCE:          [./t.sh]
$BASH_SOURCE or $0:    [./t.sh]
$BASH_SOURCE[0] or $0: [./t.sh]

$ bash t.sh
$0:                    [t.sh]
$BASH_SOURCE:          [t.sh]
$BASH_SOURCE or $0:    [t.sh]
$BASH_SOURCE[0] or $0: [t.sh]

最后:

$ sh t.sh
$0:                    [t.sh]
$BASH_SOURCE:          []
$BASH_SOURCE or $0:    [t.sh]
t.sh: 6: t.sh: Bad substitution

重新开始

如您所见,只有第三个变体:${BASH_SOURCE:-$0}在所有调用场景下都有效并给出一致的结果。注意,我们利用了bash的特性,即引用一个等于第一个数组元素的未下标数组变量。

其他回答

为了可移植性,在定义时使用${BASH_SOURCE[0]},否则使用$0。这给了

${BASH_SOURCE[0]:-$0}

值得注意的是,在zsh中,$0包含正确的文件路径,即使脚本是源文件。

注意:对于posix兼容的解决方案,请参阅此答案。

${BASH_SOURCE[0]}(或者更简单地说,$BASH_SOURCE[1] )包含所有调用场景中包含脚本的路径(可能是相对的),特别是当脚本是源脚本时,这对于$0来说不是真的。

此外,正如Charles Duffy指出的那样,调用者可以将$0设置为任意值。 另一方面,如果没有命名文件,$BASH_SOURCE可以为空;例如: echo 'echo "[$BASH_SOURCE]"' | bash . sh

下面的例子说明了这一点:

脚本foo:

#!/bin/bash
echo "[$0] vs. [${BASH_SOURCE[0]}]"

$ bash ./foo
[./foo] vs. [./foo]

$ ./foo
[./foo] vs. [./foo]

$ . ./foo
[bash] vs. [./foo]

$0是POSIX shell规范的一部分,而BASH_SOURCE,顾名思义,是特定于bash的。


[1]可选读取:${BASH_SOURCE[0]} vs. $BASH_SOURCE:

Bash允许您使用标量符号引用数组变量的元素0:不用编写${arr[0]},您可以编写$arr;换句话说:如果你引用变量,就好像它是一个标量,你得到的元素的下标为0。

使用这个特性掩盖了$arr是一个数组的事实,这就是为什么流行的shell-code linter shellcheck.net会发出以下警告(在撰写本文时):

SC2128:展开一个没有索引的数组只给出第一个元素。

附注:虽然这个警告是有用的,但它可以更精确,因为您不一定会得到第一个元素:它返回的是索引为0的元素,因此如果第一个元素具有更高的索引(这在Bash中是可能的),您将得到空字符串;试一试[1]=“嗨”;回声“美元”。 (相比之下,zsh,始终是叛逆者,将所有元素作为一个单独的字符串返回,以第一个字符分隔。存储在$IFS中,默认为空格)。

由于它的模糊性,您可以选择避开这个特性,但它的工作方式是可以预见的,而且从实际意义上讲,您几乎不需要访问数组变量${BASH_SOURCE[@]}的除0以外的索引。


可选阅读,第2部分:BASH_SOURCE数组变量在什么条件下实际上包含多个元素?:

BASH_SOURCE只有在涉及函数调用时才有多个条目,在这种情况下,它的元素与包含当前调用堆栈上所有函数名的FUNCNAME数组并行。

也就是说,在函数内部,${FUNCNAME[0]}包含执行函数的名称,${BASH_SOURCE[0]}包含定义该函数的脚本文件的路径,${FUNCNAME[1]}包含调用当前执行函数的函数名(如果适用的话),等等。

如果一个给定的函数是直接从脚本文件的顶层作用域调用的,该脚本文件在调用堆栈的$i级定义了函数,${FUNCNAME[$i+1]}包含:

Main(一个伪函数名),如果直接调用脚本文件(例如,./script) Source(伪函数名),如果脚本文件是源文件(例如Source ./script或. ./script)。

我建议使用${BASH_SOURCE:-$0}作为最通用的变体。

之前的答案很好,但他们没有提到直接使用${BASH_SOURCE[0]}的一个警告:如果你调用脚本作为sh的参数,并且你的sh不是bash的alias(在我的情况下,在Ubuntu 16.04.5 LTS上,它链接到dash), BASH_SOURCE变量为空/未定义可能会失败。这里有一个例子:

t.sh:

#!/usr/bin/env bash

echo "\$0:                     [$0]"
echo "\$BASH_SOURCE:           [$BASH_SOURCE]"
echo "\$BASH_SOURCE or \$0:    [${BASH_SOURCE:-$0}]"
echo "\$BASH_SOURCE[0] or \$0: [${BASH_SOURCE[0]:-$0}]"

(成功地)运行:

$ ./t.sh
$0:                    [./t.sh]
$BASH_SOURCE:          [./t.sh]
$BASH_SOURCE or $0:    [./t.sh]
$BASH_SOURCE[0] or $0: [./t.sh]

$ source ./t.sh
$0:                    [/bin/bash]
$BASH_SOURCE:          [./t.sh]
$BASH_SOURCE or $0:    [./t.sh]
$BASH_SOURCE[0] or $0: [./t.sh]

$ bash t.sh
$0:                    [t.sh]
$BASH_SOURCE:          [t.sh]
$BASH_SOURCE or $0:    [t.sh]
$BASH_SOURCE[0] or $0: [t.sh]

最后:

$ sh t.sh
$0:                    [t.sh]
$BASH_SOURCE:          []
$BASH_SOURCE or $0:    [t.sh]
t.sh: 6: t.sh: Bad substitution

重新开始

如您所见,只有第三个变体:${BASH_SOURCE:-$0}在所有调用场景下都有效并给出一致的结果。注意,我们利用了bash的特性,即引用一个等于第一个数组元素的未下标数组变量。

这些脚本可能有助于说明。外部脚本调用中间脚本,中间脚本调用内部脚本:

$ cat outer.sh
#!/usr/bin/env bash
./middle.sh
$ cat middle.sh
#!/usr/bin/env bash
./inner.sh
$ cat inner.sh
#!/usr/bin/env bash
echo "\$0 = '$0'"
echo "\${BASH_SOURCE[0]} = '${BASH_SOURCE[0]}'"
echo "\${BASH_SOURCE[1]} = '${BASH_SOURCE[1]}'"
echo "\${BASH_SOURCE[2]} = '${BASH_SOURCE[2]}'"
$ ./outer.sh
$0 = './inner.sh'
$BASH_SOURCE[0] = './inner.sh'
$BASH_SOURCE[1] = ''
$BASH_SOURCE[2] = ''

然而,如果我们改变脚本对源语句的调用:

$ cat outer.sh
#!/usr/bin/env bash
source ./middle.sh
$ cat middle.sh
#!/usr/bin/env bash
source ./inner.sh
$ cat inner.sh
#!/usr/bin/env bash
echo "\$0 = '$0'"
echo "\${BASH_SOURCE[0]} = '${BASH_SOURCE[0]}'"
echo "\${BASH_SOURCE[1]} = '${BASH_SOURCE[1]}'"
echo "\${BASH_SOURCE[2]} = '${BASH_SOURCE[2]}'"
$ ./outer.sh
$0 = './outer.sh'
$BASH_SOURCE[0] = './inner.sh'
$BASH_SOURCE[1] = './middle.sh'
$BASH_SOURCE[2] = './outer.sh'