如何检测脚本是否被find

我有一个脚本,我不希望它调用exit如果它是来源。 最初,我虽然检查$0 == bash但如果脚本来自另一个脚本,或者如果用户从ksh源它,这有问题。 是否有一个可靠的方法来检测脚本是否来源?

这在Bash和Korn之间似乎是可移植的:

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

一个类似于这样的代码行或者像`pathname =“$ _”(后面的testing和操作)的任务必须位于脚本的第一行或shebang之后的行(如果使用的话,应该是ksh为了在大多数情况下工作)。

如果您的Bash版本知道BASH_SOURCE数组variables,请尝试如下所示:

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

在阅读@ DennisWilliamson的回答后,有一些问题,请看下面:

由于这个问题代表ksh bash ,在这个答案中还有另外一部分关于ksh …见下面。

简单的bash方式

 [ "$0" = "$BASH_SOURCE" ] 

让我们试试(因为这个bash可以;-):

 source <(echo $'#!/bin/bash [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced; echo "process $$ is $v ($0, $BASH_SOURCE)" ') process 29301 is sourced (bash, /dev/fd/63) bash <(echo $'#!/bin/bash [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced; echo "process $$ is $v ($0, $BASH_SOURCE)" ') process 16229 is own (/dev/fd/63, /dev/fd/63) 

我使用source而不是. 为了可读性(as是source的别名):

 . <(echo $'#!/bin/bash [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced; echo "process $$ is $v ($0, $BASH_SOURCE)" ') process 29301 is sourced (bash, /dev/fd/63) 

请注意,进程保持来源时,进程号不会更改:

 echo $$ 29301 

为什么不用$_ == $0比较

为了确保很多情况下,我开始写一个真正的脚本:

 #!/bin/bash # As $_ could be used only once, uncomment one of two following lines #printf '_="%s", 0="%s" and BASH_SOURCE="%s"\n' "$_" "$0" "$BASH_SOURCE" [[ "$_" != "$0" ]] && DW_PURPOSE=sourced || DW_PURPOSE=subshell [ "$0" = "$BASH_SOURCE" ] && BASH_KIND_ENV=own || BASH_KIND_ENV=sourced; echo "proc: $$[ppid:$PPID] is $BASH_KIND_ENV (DW purpose: $DW_PURPOSE)" 

将其复制到一个名为testscript的文件中:

 cat >testscript chmod +x testscript 

现在我们可以testing:

 ./testscript proc: 25758[ppid:24890] is own (DW purpose: subshell) 

没关系。

 . ./testscript proc: 24890[ppid:24885] is sourced (DW purpose: sourced) source ./testscript proc: 24890[ppid:24885] is sourced (DW purpose: sourced) 

没关系。

但是,为了在添加-x标志之前testing脚本:

 bash ./testscript proc: 25776[ppid:24890] is own (DW purpose: sourced) 

或者使用预定义的variables:

 env PATH=/tmp/bintemp:$PATH ./testscript proc: 25948[ppid:24890] is own (DW purpose: sourced) env SOMETHING=PREDEFINED ./testscript proc: 25972[ppid:24890] is own (DW purpose: sourced) 

这将不再工作。

将评论从第5行移到第6行将会给出更可读的答案:

 ./testscript _="./testscript", 0="./testscript" and BASH_SOURCE="./testscript" proc: 26256[ppid:24890] is own . testscript _="_filedir", 0="bash" and BASH_SOURCE="testscript" proc: 24890[ppid:24885] is sourced source testscript _="_filedir", 0="bash" and BASH_SOURCE="testscript" proc: 24890[ppid:24885] is sourced bash testscript _="/bin/bash", 0="testscript" and BASH_SOURCE="testscript" proc: 26317[ppid:24890] is own env FILE=/dev/null ./testscript _="/usr/bin/env", 0="./testscript" and BASH_SOURCE="./testscript" proc: 26336[ppid:24890] is own 

更难: ksh现在…

因为我没有使用ksh很多,在man页面上阅读了一些后,有我的尝试:

 #!/bin/ksh set >/tmp/ksh-$$.log 

将其复制到testfile.ksh

 cat >testfile.ksh chmod +x testfile.ksh 

比运行它两次:

 ./testfile.ksh . ./testfile.ksh ls -l /tmp/ksh-*.log -rw-r--r-- 1 user user 2183 avr 11 13:48 /tmp/ksh-9725.log -rw-r--r-- 1 user user 2140 avr 11 13:48 /tmp/ksh-9781.log echo $$ 9725 

并看到:

 diff /tmp/ksh-{9725,9781}.log | grep ^\> # OWN SUBSHELL: > HISTCMD=0 > PPID=9725 > RANDOM=1626 > SECONDS=0.001 > lineno=0 > SHLVL=3 diff /tmp/ksh-{9725,9781}.log | grep ^\< # SOURCED: < COLUMNS=152 < HISTCMD=117 < LINES=47 < PPID=9163 < PS1='$ ' < RANDOM=29667 < SECONDS=23.652 < level=1 < lineno=1 < SHLVL=2 

源代码中有一些variables是遗漏的,但是没有什么关系。

你甚至可以检查$SECONDS是接近于0.000 ,但这确保只有手工来源的情况下…

你甚至可以尝试检查父母是什么

把它放到testfile.ksh

 ps $PPID 

比:

 ./testfile.ksh PID TTY STAT TIME COMMAND 32320 pts/4 Ss 0:00 -ksh . ./testfile.ksh PID TTY STAT TIME COMMAND 32319 ? S 0:00 sshd: user@pts/4 

ps ho cmd $PPID ,但这只适用于子级别的一个级别…

对不起,在ksh下我找不到一个可靠的方法。

bashkshzsh提供了强大的解决scheme ,包括一个跨shell的 解决scheme ,以及一个相当强大的符合POSIX标准的解决scheme

  • 给出的版本号码是validationfunction的版本号码,可能这些解决scheme也适用于更早的版本, 欢迎提供反馈意见

  • 使用POSIXfunction (例如在dash ,在Ubuntu上充当/bin/sh ), 没有可靠的方法来确定脚本是否来源 – 请参阅此答案的底部以获得最佳近似值

单行遵循 – 下面的解释; 跨壳版本是复杂的,但它应该强健地工作:

  • bash (在3.57validation)

     [[ $0 != "$BASH_SOURCE" ]] && sourced=1 || sourced=0 
  • ksh (在93u +validation)

     [[ $(cd "$(dirname -- "$0")" && printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] && sourced=1 || sourced=0 
  • zsh (在5.0.5validation) – 一定要在函数之外调用它

     [[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0 
  • 跨壳(bash,ksh,zsh)

     ([[ -n $ZSH_EVAL_CONTEXT && $ZSH_EVAL_CONTEXT =~ :file$ ]] || [[ -n $KSH_VERSION && $(cd "$(dirname -- "$0")" && printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] || [[ -n $BASH_VERSION && $0 != "$BASH_SOURCE" ]]) && sourced=1 || sourced=0 
  • [不鲁棒]仅使用POSIXfunction :请参阅底部


说明:


庆典

 [[ "$0" != "$BASH_SOURCE" ]] && sourced=1 || sourced=0 
  • $BASH_SOURCE … ALWAYS包含脚本文件参数,无论是否来源。
  • $0
    • 如果没有来源: 总是$BASH_SOURCE相同
    • 如果来源:
      • 如果源自另一个脚本:脚本来自哪个脚本。
      • 如果来源交互:
        • 通常情况下, bash为非loginshell, -bash为loginshell(例如在OSX上),或者,如果Bash被调用为sh ,则类似于sh-sh
        • 但是,如果Bash以相对或绝对path(直接)开始,那么这个path将反映在$0
        • 还要注意,可以用$0的任意值启动Bash(或任何程序),比如通过使用exec内build和-a选项。

KSH

 [[ \ $(cd "$(dirname -- "$0")" && printf '%s' "${PWD%/}/")$(basename -- "$0") != \ "${.sh.file}" \ ]] && sourced=1 || sourced=0 

特殊variables${.sh.file}有点类似于$BASH_SOURCE ; 请注意, ${.sh.file}在bash,zsh和dash中会导致语法错误 ,因此请务必在多shell脚本中有条件地执行它。

与bash不同的是, $0${.sh.file}不能保证在非源代码中完全一样,因为$0可能是一个相对path,而${.sh.file}总是一个完整path,所以比较之前,必须将$0parsing为完整path。


zsh的

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

$ZSH_EVAL_CONTEXT包含关于评估上下文的信息 – 在函数之外调用它。 在源脚本[的顶级范围]内, $ZSH_EVAL_CONTEXT:file 结尾

警告:在一个命令replace中,zsh追加:cmdsubst ,所以testing$ZSH_EVAL_CONTEXT:file:cmdsubst$ there。


仅使用POSIXfunction

如果您愿意做出某些假设,您可以基于知道可能正在执行脚本的shell的文件名,对脚本是否来源进行合理的猜测但不是傻瓜式的猜测)
值得注意的是,这意味着如果你的脚本被另一个脚本来源,这种方法失败。

本答案中的“如何处理源调用”一节讨论了POSIXfunction无法处理的边缘情况。

依赖于$0的标准行为,例如zsh没有performance出来。

因此,最安全的方法是将上面强大的特定于shell的方法与其余所有shell 后备解决scheme相结合

( StéphaneDesneux)和他的灵感答案 (将我的跨壳声明expression式转换为sh – compatible声明并为其他声明添加处理程序)。

 sourced=0 if [ -n "$ZSH_EVAL_CONTEXT" ]; 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 [ "$0" != "$BASH_SOURCE" ] && 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|dash) sourced=1;; esac fi 

BASH_SOURCE[]答案(bash-3.0及更高版本)似乎最简单,虽然BASH_SOURCE[] 没有logging在函数体外工作 (它现在正常工作,不同意手册页)。

正如Wirawan Purwanto所build议的那样,最强大的方法是在函数中检查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")' 

这相当于检查caller的输出, main值和source的值区分调用者的上下文。 使用FUNCNAME[]可以保存捕获和parsingcaller输出。 您需要知道或计算您的本地通话深度是正确的。 像另一个函数或脚本内源脚本的情况下会导致数组(堆栈)更深。 ( FUNCNAME是一个特殊的bash数组variables,只要它不被unset ,它就应该有与调用栈相对应的连续索引。)

 function issourced() { [[ ${FUNCNAME[@]: -1} == "source" ]] } 

(在bash-4.2及更高版本中,您可以使用简单的forms${FUNCNAME[-1]}来代替数组中的最后一项。由于下面的Dennis Williamson的注释,改进和简化。

然而,你所说的问题是“ 我有一个脚本,我不希望它叫'退出',如果它是来源 ”。 这种情况常见的bash成语是:

 return 2>/dev/null || exit 

如果脚本正在发送,则return将终止源脚本并返回给调用者。

如果脚本正在执行,则return将返回一个错误(redirect), exit将正常结束脚本。 如果需要, returnexit都可以退出代码。

可悲的是,这在ksh不起作用(至less不是在AT&T衍生版本中),它将return视为等价于在函数或点源脚本之外调用时exit

更新 :在当代版本的ksh可以做的是检查设置为函数调用深度的特殊variables.sh.level 。 对于一个被调用的脚本,它最初将被取消设置,对于一个点源脚本,它将被设置为1。

 function issourced { [[ ${.sh.level} -eq 2 ]] } issourced && echo this script is sourced 

这不像bash版本那么强大,你必须在你正在testing的文件的顶层或者已知函数深度调用issourced()

(你也可能对github上的代码感兴趣,它使用ksh纪律函数和一些debugging陷阱欺骗来模拟bash FUNCNAME数组。

这个规范的答案在这里: http : //mywiki.wooledge.org/BashFAQ/109也提供了$-作为壳状态的另一个指标(尽pipe不完美)。


笔记:

  • 可以创build名为“main”和“source”的bash函数( 覆盖内build函数),这些名称可以出现在FUNCNAME[]但只要testing该数组中的最后一项,就没有歧义。
  • 我没有一个好的答案pdksh 。 我能find的最接近的东西只适用于pdksh ,其中脚本的每个源打开一个新的文件描述符(从原始脚本开始)。 几乎肯定不是你想要的东西…

TL; DR

尝试执行一个return语句。 如果脚本没有find,那会引发错误。 您可以捕获该错误,并根据需要进行处理。

把它放在一个文件中,然后调用它,比如test.sh:

 #!/usr/bin/env sh # Try to execute a `return` statement, # but do it in a sub-shell and catch the results. # If this script isn't sourced, that will raise an error. $(return >/dev/null 2>&1) # What exit code did that give? if [ "$?" -eq "0" ] then echo "This script is sourced." else echo "This script is not sourced." fi 

直接执行:

 shell-prompt> sh test.sh output: This script is not sourced. 

来源:

 shell-prompt> source test.sh output: This script is sourced. 

对我来说,这个工作在zsh和bash中。

说明

如果您尝试在函数之外执行它,或者脚本未find,则return语句将引发错误。 从shell提示符下试试这个:

 shell-prompt> return output: ...can only `return` from a function or sourced script 

您不需要看到该错误消息,因此您可以将输出redirect到dev / null:

 shell-prompt> return >/dev/null 2>&1 

现在检查退出代码。 0表示正常(没有错误发生),1表示发生错误:

 shell-prompt> echo $? output: 1 

你也想在子shell中执行return语句。 当return语句运行它时。 。 。 好 。 。 。 回报。 如果你在一个子shell中执行它,它会从这个子shell中返回,而不是从你的脚本中退出。 要在子shell中执行,请将其包装在$(...)

 shell-prompt> $(return >/dev/null 2>$1) 

现在,您可以看到子shell的退出代码,它应该是1,因为在子shell中引发了一个错误:

 shell-prompt> echo $? output: 1 

我将给出一个BASH特定的答案。 科恩壳,对不起。 假设你的脚本名是include2.sh ; 然后在include2.sh创build一个名为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的内省function,即内置的variablesFUNCNAMEBASH_SOURCE ; 请参阅bash手册页面中的文档。

只有两个警告:

1)对am_I_called的调用必须 源脚本中进行,但不能在任何函数中进行,以免${FUNCNAME[1]}返回其他内容。 是啊…你可以检查${FUNCNAME[2]} – 但你只是让你的生活更难。

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

我想build议对丹尼斯的非常有帮助的回答进行一个小小的更正,让它更轻便一点,我希望:

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

因为[[不被有些肛门保留的IMHO)Debian POSIX兼容 shell, dash识别。 此外,可能还需要引号来防止包含空格的文件名,再次在所述shell中。

这在稍后的脚本中起作用,并且不依赖于_variables:

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

要么

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

$_非常脆弱。 你必须把它作为你在脚本中做的第一件事来检查。 即使如此,也不保证包含你的shell的名字(如果有的话)或者脚本的名字(如果被执行的话)。

例如,如果用户已经设置了BASH_ENV ,那么在脚本的顶部, $_包含在BASH_ENV脚本中执行的最后一个命令的名称。

我发现的最好的方法是使用$0像这样:

 name="myscript.sh" main() { echo "Script was executed, running main..." } case "$0" in *$name) main "$@" ;; esac 

不幸的是,这种方式在zsh中并不适用,因为functionargzero选项的functionargzero超过了它的名字,并且默认情况下处于打开状态。

要解决这个问题,我把我的.zshenv

我跟着mklement0压缩expression式 。

这是整洁的,但我注意到,它可以失败的情况下ksh时调用这样的:

 /bin/ksh -c ./myscript.sh 

(它认为它是来源,这不是因为它执行一个子shell)但expression式将工作来检测:

 /bin/ksh ./myscript.sh 

而且,即使expression式是紧凑的,语法也不兼容所有的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 

随意添加异国情调的炮弹支持:)

直接点:您必须评估variables“$ 0”是否等于您的壳牌的名称。

喜欢这个:

 #!/bin/bash echo "First Parameter: $0" echo if [[ "$0" == "bash" ]] ; then echo "The script was sourced." else echo "The script WAS NOT sourced." fi 

通过shell

 $ bash check_source.sh First Parameter: check_source.sh The script WAS NOT sourced. 

通过消息来源

 $ source check_source.sh First Parameter: bash The script was sourced. 


很难有一个100%的可移植的方式来检测脚本是否来源。

关于我的经验(7年Shellscripting) ,唯一安全的方法(不依赖于PID等环境variables,由于它是VARIABLE而不安全),您应该:

  • 从你的if扩展可能性
  • 使用开关/shell,如果你想。

这两个选项都不能自动缩放,但这是更安全的方法。


例如:

当您通过SSH会话获取脚本时,variables“$ 0” (使用源代码 )返回的值是-bash

 #!/bin/bash echo "First Parameter: $0" echo if [[ "$0" == "bash" || "$0" == "-bash" ]] ; then echo "The script was sourced." else echo "The script WAS NOT sourced." fi 

要么

 #!/bin/bash echo "First Parameter: $0" echo if [[ "$0" == "bash" ]] ; then echo "The script was sourced." elif [[ "$0" == "-bash" ]] ; then echo "The script was sourced via SSH session." else echo "The script WAS NOT sourced." fi 

我不认为在ksh和bash中都有这样的可移植的方法。 在bash中,你可以使用caller输出来检测它,但是我不认为在ksh中存在等价物。

我需要一个在bash.version> = 3的[mac,linux]上运行的单线程,这些答案都不符合要求。

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