我有一个脚本,我不希望它调用退出,如果它是来源。

我想检查是否$0 == bash,但这有问题,如果脚本是从另一个脚本,或者如果用户从不同的shell,如ksh源。

是否有一种可靠的方法来检测脚本是否被引用?


当前回答

我将给出一个特定于bash的答案。Korn shell,对不起。假设脚本名为include2.sh;然后在include2.sh中创建一个名为am_I_sourced的函数。下面是我的include2.sh的演示版本:

am_I_sourced()
{
  if [ "${FUNCNAME[1]}" = source ]; then
    if [ "$1" = -v ]; then
      echo "I am being sourced, this filename is ${BASH_SOURCE[0]} and my caller script/shell name was $0"
    fi
    return 0
  else
    if [ "$1" = -v ]; then
      echo "I am not being sourced, my script/shell name was $0"
    fi
    return 1
  fi
}

if am_I_sourced -v; then
  echo "Do something with sourced script"
else
  echo "Do something with executed script"
fi

现在尝试以多种方式执行它:

~/toys/bash $ chmod a+x include2.sh

~/toys/bash $ ./include2.sh 
I am not being sourced, my script/shell name was ./include2.sh
Do something with executed script

~/toys/bash $ bash ./include2.sh 
I am not being sourced, my script/shell name was ./include2.sh
Do something with executed script

~/toys/bash $ . include2.sh
I am being sourced, this filename is include2.sh and my caller script/shell name was bash
Do something with sourced script

所以这是毫无例外的工作,它没有使用脆弱的$_东西。这个技巧使用了BASH的自省功能,即内置变量FUNCNAME和BASH_SOURCE;请参阅bash手册页中的文档。

只有两个警告:

1)对am_I_called的调用必须发生在源脚本中,而不是在任何函数中,以免${FUNCNAME[1]}返回其他东西。是的…您本可以检查${FUNCNAME[2]},但这样做只会使您的工作更加困难。

2)函数am_I_called必须驻留在源脚本中,如果你想知道被包含的文件的名称。

其他回答

我遵循mklement0紧凑表达式。

这很整洁,但我注意到,当调用ksh时,它可能会失败:

/bin/ksh -c ./myscript.sh

(它认为它是源的,而不是因为它执行了一个子shell) 但是表达式可以检测到这一点:

/bin/ksh ./myscript.sh

此外,即使表达式是紧凑的,语法也不兼容所有shell。

因此,我以以下代码结束,它适用于bash,zsh,dash和ksh

SOURCED=0
if [ -n "$ZSH_EVAL_CONTEXT" ]; then 
    [[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && SOURCED=1
elif [ -n "$KSH_VERSION" ]; then
    [[ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ]] && SOURCED=1
elif [ -n "$BASH_VERSION" ]; then
    [[ $0 != "$BASH_SOURCE" ]] && SOURCED=1
elif grep -q dash /proc/$$/cmdline; then
    case $0 in *dash*) SOURCED=1 ;; esac
fi

请随意添加异国情调的贝壳支持:)

如果您的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 ..."

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开始)。几乎肯定不是你想要依靠的东西……

我想对丹尼斯非常有用的回答提出一个小小的更正,让它更容易携带,我希望:

[ "$_" != "$0" ] && echo "Script is being sourced" || echo "Script is a subshell"

因为[[不被Debian POSIX兼容外壳识别(有些保留的IMHO), dash。同样,在shell中,可能需要使用引号来防止文件名中包含空格。

这在后面的脚本中起作用,不依赖于_变量:

## Check to make sure it is not sourced:
Prog=myscript.sh
if [ $(basename $0) = $Prog ]; then
   exit 1  # not sourced
fi

or

[ $(basename $0) = $Prog ] && exit