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

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

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


当前回答

我需要一个在[mac, linux]上使用bash的一行程序。版本>= 3,这些答案都不符合要求。

[[ ${BASH_SOURCE[0]} = $0 ]] && main "$@"

其他回答

bash, ksh, zsh的健壮解决方案,包括一个跨shell解决方案,以及一个相当健壮的posix兼容解决方案:

给出的版本号是验证功能的版本号——很可能,这些解决方案也适用于更早的版本——欢迎反馈。 仅使用POSIX特性(例如在dash中,它在Ubuntu中充当/bin/sh),没有可靠的方法来确定脚本是否被引用-请参阅下面的最佳近似方法。

重要的是:

The solutions determine whether the script is being sourced by its caller, which may be a shell itself or another script (which may or may not be sourced itself): Also detecting the latter case adds complexity; if you do not need to detect the case when your script is being sourced by another script, you can use the following, relatively simple POSIX-compliant solution: # Helper function is_sourced() { if [ -n "$ZSH_VERSION" ]; then case $ZSH_EVAL_CONTEXT in *:file:*) return 0;; esac else # Add additional POSIX-compatible shell names here, if needed. case ${0##*/} in dash|-dash|bash|-bash|ksh|-ksh|sh|-sh) return 0;; esac fi return 1 # NOT sourced. } # Sample call. is_sourced && sourced=1 || sourced=0 All solutions below must run in the top-level scope of your script, not inside functions.

下面是一行程序——解释如下;跨shell版本是复杂的,但它应该可以健壮地工作:

Bash(在3.57、4.4.19和5.1.16上验证)

(return 0 2>/dev/null) && sourced=1 || sourced=0

KSH(在93u+上验证)

[[ "$(cd -- "$(dirname -- "$0")" && pwd -P)/$(basename -- "$0")" != "$(cd -- "$(dirname -- "${.sh.file}")" && pwd -P)/$(basename -- "${.sh.file}")" ]] && sourced=1 || sourced=0

ZSH(5.0.5验证)

[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0

交叉shell (bash, ksh, zsh)

(
  [[ -n $ZSH_VERSION && $ZSH_EVAL_CONTEXT =~ :file$ ]] || 
  [[ -n $KSH_VERSION && "$(cd -- "$(dirname -- "$0")" && pwd -P)/$(basename -- "$0")" != "$(cd -- "$(dirname -- "${.sh.file}")" && pwd -P)/$(basename -- "${.sh.file}")" ]] || 
  [[ -n $BASH_VERSION ]] && (return 0 2>/dev/null)
) && sourced=1 || sourced=0

posix兼容;由于技术原因,不是一行程序(单一管道),并且不完全健壮(见底部):

sourced=0
if [ -n "$ZSH_VERSION" ]; then 
  case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
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
  (return 0 2>/dev/null) && sourced=1 
else # All other shells: examine $0 for known shell binary filenames.
     # Detects `sh` and `dash`; add additional shell filenames as needed.
  case ${0##*/} in sh|-sh|dash|-dash) sourced=1;; esac
fi

解释


bash

(return 0 2>/dev/null) && sourced=1 || sourced=0

注意:该技术改编自user5754163的答案,因为它比原来的解决方案更健壮,[[$0 != "$BASH_SOURCE"]] && sourced=1 || sourced=0[1]

Bash allows return statements only from functions and, in a script's top-level scope, only if the script is sourced. If return is used in the top-level scope of a non-sourced script, an error message is emitted, and the exit code is set to 1. (return 0 2>/dev/null) executes return in a subshell and suppresses the error message; afterwards the exit code indicates whether the script was sourced (0) or not (1), which is used with the && and || operators to set the sourced variable accordingly. Use of a subshell is necessary, because executing return in the top-level scope of a sourced script would exit the script. Tip of the hat to @Haozhun, who made the command more robust by explicitly using 0 as the return operand; he notes: per bash help of return [N]: "If N is omitted, the return status is that of the last command." As a result, the earlier version [which used just return, without an operand] produces incorrect result if the last command on the user's shell has a non-zero return value.


ksh

[[ "$(cd -- "$(dirname -- "$0")" && pwd -P)/$(basename -- "$0")" != "$(cd -- "$(dirname -- "${.sh.file}")" && pwd -P)/$(basename -- "${.sh.file}")" ]] && sourced=1 || sourced=0

特殊变量${.sh。file}有点类似于$BASH_SOURCE;注意${.sh。File}会在bash、zsh和dash中导致语法错误,所以一定要在多shell脚本中有条件地执行它。

与bash不同,$0和${.sh。file}不能保证是相同的-在不同的时候,一个可能是相对路径或仅仅是文件名,而另一个可能是完整的文件名;因此,$0和${.sh。File}在比较之前必须解析为全路径。如果完整路径不同,则隐含了源。


zsh

[[ $ZSH_EVAL_CONTEXT =~ :file$) ]] && sourced=1 || sourced=0

$ZSH_EVAL_CONTEXT包含关于计算上下文:子字符串文件的信息,用:分隔,只有在脚本被引用时才会出现。

在源脚本的顶级作用域中,$ZSH_EVAL_CONTEXT以:file结尾,这就是这个测试的限制。在函数内部,:shfunc被追加到:file;在命令替换中追加:cmdsubst。


仅使用POSIX特性

如果您愿意做出某些假设,那么您可以根据可能正在执行脚本的shell的二进制文件名,对脚本是否被引用做出合理但并非万无一错的猜测。 值得注意的是,这意味着当您的脚本被另一个脚本引用时,这种方法不会检测到这种情况。

本回答中的“如何处理源调用”一节只详细讨论了POSIX特性无法处理的边缘情况。

检查二进制文件名依赖于$0的标准行为,例如,zsh就没有这种行为。

因此,最安全的方法是将上述健壮的、特定于shell的方法(不依赖于$0)与所有剩余shell的基于$0的回退解决方案结合起来。

简而言之:解决方案如下:

在包含特定于shell的测试的shell中:稳定地工作。 在所有其他shell中:仅当脚本直接来自这样的shell(而不是来自另一个脚本)时才能正常工作。

向Stéphane Desneux和他的灵感答案致敬(将我的跨shell语句表达式转换为sh兼容的if语句,并为其他shell添加一个处理程序)。

sourced=0
if [ -n "$ZSH_VERSION" ]; then 
  case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
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
  (return 0 2>/dev/null) && sourced=1 
else # All other shells: examine $0 for known shell binary filenames.
  # Detects `sh` and `dash`; add additional shell filenames as needed.
  case ${0##*/} in sh|-sh|dash|-dash) sourced=1;; esac
fi

注意,为了健壮性,每个shell二进制文件名(例如sh)都表示了两次——一次是原样,第二次是,前缀是-。这是为了考虑诸如macOS这样的环境,其中交互式shell作为登录shell启动,其自定义值为$0,该值是前缀为-的(无路径)shell文件名。谢谢,t7e。 (虽然sh和dash可能不太可能用作交互式shell,但您可能需要将其他shell添加到列表中。)


[1] user1902689发现[[$0 != "$BASH_SOURCE"]]在通过将其文件名传递给bash二进制文件执行位于$PATH中的脚本时,会产生假阳性;例如,bash my-script,因为$0只是my-script,而$BASH_SOURCE是完整路径。虽然您通常不会使用这种技术来调用$PATH中的脚本—您只会直接调用它们(my-script)—当与-x结合使用时,它对调试很有帮助。

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

## 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

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

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

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

这是从其他一些关于“通用”跨壳支持的答案衍生出来的。不可否认,这与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脚本是否正在执行或导入的最漂亮方法

我真的认为这是最美丽的方式:

从我的eRCaGuy_hello_world repo中的if__name__==__main__ check_if_sourced_or_executed_best.sh文件:

#!/usr/bin/env bash

main() {
    echo "Running main."
    # Add your main function code here
}

if [ "${BASH_SOURCE[0]}" = "$0" ]; then
    # This script is being run.
    __name__="__main__"
else
    # This script is being sourced.
    __name__="__source__"
fi

# Only run `main` if this script is being **run**, NOT sourced (imported)
if [ "$__name__" = "__main__" ]; then
    echo "This script is being run."
    main
else
    echo "This script is being sourced."
fi

引用:

关于上述技术的其他详细信息,请参见我在这里的另一个回答,包括显示运行输出:bash与Python的if __name__ == '__main__'等价于什么? 这个答案,我第一次了解到“${BASH_SOURCE[0]}”=“$0”

如果您愿意,还可以探索以下替代方案,但我更喜欢使用上面的代码块。

重要提示:使用“${FUNCNAME[-1]}”技术不能正确处理嵌套脚本,即一个脚本调用或来源另一个脚本,而if ["${BASH_SOURCE[0]}" = "$0"]技术可以。这是使用if ["${BASH_SOURCE[0]}" = "$0"]的另一个重要原因。

4种方法确定bash脚本是源脚本还是执行脚本

我已经阅读了关于这个问题和其他一些问题的一堆答案,并提出了4种我想要总结并放在一个地方的方法。

if __name__ == "__main__":

参见:如果__name__ == "__main__":会做什么?在Python中所做的事情。

You can see a full demonstration of all 4 techniques below in my check_if_sourced_or_executed.sh script in my eRCaGuy_hello_world repo. You can see one of the techniques in-use in my advanced bash program with help menu, argument parsing, main function, automatic execute vs source detection (akin to if __name__ == "__main__": in Python), etc, see my demo/template program in this list here. It is currently called argument_parsing__3_advanced__gen_prog_template.sh, but if that name changes in the future I'll update it in the list at the link just above

不管怎样,这里有4个Bash技术:

Technique 1 (can be placed anywhere; handles nested scripts): See: https://unix.stackexchange.com/questions/424492/how-to-define-a-shell-script-to-be-sourced-not-run/424495#424495 if [ "${BASH_SOURCE[0]}" -ef "$0" ]; then echo " This script is being EXECUTED." run="true" else echo " This script is being SOURCED." fi Technique 2 [My favorite technique] (can be placed anywhere; handles nestes scripts): See this type of technique in-use in my most-advanced bash demo script yet, here: argument_parsing__3_advanced__gen_prog_template.sh, near the bottom. Modified from: What is the bash equivalent to Python's `if __name__ == '__main__'`? if [ "${BASH_SOURCE[0]}" == "$0" ]; then echo " This script is being EXECUTED." run="true" else echo " This script is being SOURCED." fi Technique 3 (requires another line which MUST be outside all functions): Modified from: How to detect if a script is being sourced # A. Place this line OUTSIDE all functions: (return 0 2>/dev/null) && script_is_being_executed="false" || script_is_being_executed="true" # B. Place these lines anywhere if [ "$script_is_being_executed" == "true" ]; then echo " This script is being EXECUTED." run="true" else echo " This script is being SOURCED." fi Technique 4 [Limitation: does not handle nested scripts!] (MUST be inside a function): Modified from: How to detect if a script is being sourced and Unix & Linux: How to define a shell script to be sourced not run. if [ "${FUNCNAME[-1]}" == "main" ]; then echo " This script is being EXECUTED." run="true" elif [ "${FUNCNAME[-1]}" == "source" ]; then echo " This script is being SOURCED." else echo " ERROR: THIS TECHNIQUE IS BROKEN" fi This is where I first learned about the ${FUNCNAME[-1]} trick: @mr.spuratic: How to detect if a script is being sourced - he learned it from Dennis Williamson apparently.

参见:

[我的回答]bash相当于Python的if __name__ == '__main__'? [我的回答]Unix和Linux:如何定义一个shell脚本来获取而不是运行