如何在"$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 的功能,即引用等于第一个数组元素的无下标数组变量。