在 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]}

注意:有关符合 POSIX 的解决方案,请参阅此答案。

${BASH_SOURCE[0]}(或者更简单地说,$BASH_SOURCE [1]) 包含所有调用场景中包含脚本的(潜在相对)路径,特别是当脚本是时,这对于$0来说并非如此。

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

以下示例对此进行了说明:

脚本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]}$BASH_SOURCE

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

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

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

附带说明:虽然这个警告很有帮助,但它可能更精确,因为你不一定会得到第一个元素:它特别是返回索引0处的元素,所以如果第一个元素具有更高的索引 - 这在 Bash 中是可能的 - 你会得到空字符串;尝试a[1]='hi'; echo "$a" .
(相比之下,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)。

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

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

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

TL;DR 我建议使用 ${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 的功能,即引用等于第一个数组元素的无下标数组变量。

最新更新