在$0和BASH_SOURCE之间选择

如何在"$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]}
121716 次浏览

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

${BASH_SOURCE[0]}(或更简单地说,$BASH_SOURCE[1] )包含在所有调用场景中包含脚本的路径(可能是相对的),特别是当脚本是采购时,这对于$0不是真的 此外,正如查尔斯·达菲指出的那样,调用者可以将$0设置为任意的值。
另一方面,如果没有命名文件,$BASH_SOURCE 可以为空;例如:
echo 'echo "[$BASH_SOURCE]"' | bash < /一口> < / p >

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

脚本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] 可选读取:__ABC0 vs. $BASH_SOURCE:

Bash允许你使用标量表示法引用数组变量的元素0:你可以写$arr;换句话说:如果你引用变量就好像它是一个标量,你会得到位于0的元素。

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

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

边注:虽然这个警告是有帮助的,但它可以更精确,因为你不一定会得到第一个元素:它返回的是索引0的元素,所以如果第一个元素有更高的索引——这在Bash中是可能的——你会得到空字符串;'a[1]='hi'; echo "$a"'试试。
(相比之下,zsh,始终是叛逆者,实际上返回第一个元素,不管它的下标是什么)

你可以因为它的模糊性而选择避开这个特性,但它的工作是可预见的,而且从实用主义的角度来说,你很少(如果有的话)需要访问数组变量${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)。

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

$ 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] = ''

然而,如果我们将脚本调用改为source语句:

$ 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'

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

${BASH_SOURCE[0]:-$0}

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

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

之前的答案很好,但他们没有提到直接使用${BASH_SOURCE[0]}的一个警告:如果你调用脚本作为sh的参数,并且你的sh没有别名为bash(在我的情况下,在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的特性来引用一个等于第一个数组元素的无下标数组变量。