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

我想检查是否$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必须驻留在源脚本中,如果你想知道被包含的文件的名称。

其他回答

我认为在ksh和bash中没有任何可移植的方法来做到这一点。在bash中,您可以使用调用器输出来检测它,但我认为ksh中不存在等效的输出。

这是从其他一些关于“通用”跨壳支持的答案衍生出来的。不可否认,这与https://stackoverflow.com/a/2942183/3220983非常相似,尽管略有不同。这样做的缺点是,客户端脚本必须尊重如何使用它(即先导出一个变量)。它的优点是简单,而且可以在“任何地方”工作。这里有一个模板供你剪切和粘贴:

# NOTE: This script may be used as a standalone executable, or callable library.
# To source this script, add the following *prior* to including it:
# export ENTRY_POINT="$0"

main()
{
    echo "Running in direct executable context!"
}

if [ -z "${ENTRY_POINT}" ]; then main "$@"; fi

注意:我使用export只是为了确保这个机制可以扩展到子进程。

如果您的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和Korn之间是可移植的:

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

与此类似的行或赋值语句如pathname="$_"(带有稍后的测试和操作)必须位于脚本的第一行或shebang之后的行(如果使用了shebang,则应该用于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必须驻留在源脚本中,如果你想知道被包含的文件的名称。