我有一个脚本,我不希望它调用退出,如果它是来源。
我想检查是否$0 == bash,但这有问题,如果脚本是从另一个脚本,或者如果用户从不同的shell,如ksh源。
是否有一种可靠的方法来检测脚本是否被引用?
我有一个脚本,我不希望它调用退出,如果它是来源。
我想检查是否$0 == bash,但这有问题,如果脚本是从另一个脚本,或者如果用户从不同的shell,如ksh源。
是否有一种可靠的方法来检测脚本是否被引用?
当前回答
BASH_SOURCE[]的答案(bash-3.0及更高版本)似乎最简单,尽管BASH_SOURCE[]并没有在函数体之外工作(它目前恰好可以工作,这与手册页不一致)。
Wirawan Purwanto建议的最健壮的方法是在函数中检查FUNCNAME[1]:
function mycheck() { declare -p FUNCNAME; }
mycheck
然后:
$ bash sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="main")'
$ . sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="source")'
This is the equivalent to checking the output of caller, the values main and source distinguish the caller's context. Using FUNCNAME[] saves you capturing and parsing caller output. You need to know or calculate your local call depth to be correct though. Cases like a script being sourced from within another function or script will cause the array (stack) to be deeper. (FUNCNAME is a special bash array variable, it should have contiguous indexes corresponding to the call stack, as long as it is never unset.)
function issourced() {
[[ ${FUNCNAME[@]: -1} == "source" ]]
}
(在bash-4.2及以后版本中,可以使用更简单的形式${FUNCNAME[-1]}来代替数组中的最后一项。感谢Dennis Williamson下面的评论,改进和简化了。)
然而,你的问题如所述是“我有一个脚本,我不希望它调用'退出',如果它是来源”。这种情况下常见的bash习惯用法是:
return 2>/dev/null || exit
如果脚本是源脚本,则return将终止源脚本并返回给调用者。
如果脚本正在执行,那么return将返回一个错误(重定向),exit将正常终止脚本。如果需要,return和exit都可以使用退出码。
遗憾的是,这在ksh中不起作用(至少在我这里的AT&T派生版本中不起作用),如果在函数或点源脚本外部调用return,它将return视为等同于exit。
更新:在当前版本的ksh中,您可以检查特殊变量.sh。设置为函数调用深度的级别。对于一个被调用的脚本,它最初将被取消设置,对于一个点源脚本,它将被设置为1。
function issourced {
[[ ${.sh.level} -eq 2 ]]
}
issourced && echo this script is sourced
这并不像bash版本那样健壮,您必须在正在测试的文件的顶层或已知函数深度中调用issourced()。
(你可能也对github上的这段代码感兴趣,它使用了ksh纪律函数和一些调试陷阱技巧来模拟bash FUNCNAME数组。)
这里的标准答案是:http://mywiki.wooledge.org/BashFAQ/109还提供了$-作为shell状态的另一个指示器(尽管并不完善)。
注:
可以创建名为“main”和“source”的bash函数(覆盖内置),这些名称可能出现在FUNCNAME[]中,但只要只测试该数组中的最后一项,就不会有歧义。 我对pdksh没有一个好的答案。我能找到的最接近的方法只适用于pdksh,其中脚本的每个源都打开一个新的文件描述符(原始脚本从10开始)。几乎肯定不是你想要依靠的东西……
其他回答
我最终检查[[$_ == "$(type -p "$0")"]]
if [[ $_ == "$(type -p "$0")" ]]; then
echo I am invoked from a sub shell
else
echo I am invoked from a source command
fi
当使用curl时…| bash -s——ARGS运行远程脚本时,$0将只是bash而不是正常的/bin/bash运行实际的脚本文件,所以我使用-p“$0”来显示bash的完整路径。
测试:
curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath | bash -s -- /a/b/c/d/e /a/b/CC/DD/EE
source <(curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath)
relpath /a/b/c/d/e /a/b/CC/DD/EE
wget https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath
chmod +x relpath
./relpath /a/b/c/d/e /a/b/CC/DD/EE
我认为在ksh和bash中没有任何可移植的方法来做到这一点。在bash中,您可以使用调用器输出来检测它,但我认为ksh中不存在等效的输出。
如果您的Bash版本知道BASH_SOURCE数组变量,请尝试如下操作:
# man bash | less -p BASH_SOURCE
#[[ ${BASH_VERSINFO[0]} -le 2 ]] && echo 'No BASH_SOURCE array variable' && exit 1
[[ "${BASH_SOURCE[0]}" != "${0}" ]] && echo "script ${BASH_SOURCE[0]} is being sourced ..."
$_是很脆弱的。你必须检查它作为你在脚本中做的第一件事。即使这样,它也不保证包含shell的名称(如果是源的)或脚本的名称(如果是执行的)。
例如,如果用户设置了BASH_ENV,那么在脚本的顶部,$_包含BASH_ENV脚本中执行的最后一个命令的名称。
我发现最好的方法是像这样使用0美元:
name="myscript.sh"
main()
{
echo "Script was executed, running main..."
}
case "$0" in *$name)
main "$@"
;;
esac
不幸的是,这种方式在zsh中并不能开箱使用,因为functionargzero选项的功能超出了它的名称,并且在默认情况下是打开的。
为了解决这个问题,我把unsetopt functionarg0放在我的.zshenv中。
这似乎在Bash和Korn之间是可移植的:
[[ $_ != $0 ]] && echo "Script is being sourced" || echo "Script is a subshell"
与此类似的行或赋值语句如pathname="$_"(带有稍后的测试和操作)必须位于脚本的第一行或shebang之后的行(如果使用了shebang,则应该用于ksh,以便它在大多数情况下工作)。