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

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

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


当前回答

我最终检查[[$_ == "$(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

其他回答

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

[ "$_" != "$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

直截了当:您必须计算变量“$0”是否等于Shell的名称。

是这样的:

#!/bin/bash

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

通过壳:

$ 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%可移植的方法来检测脚本是否来自源代码。

根据我的经验(使用Shellscripting 7年),唯一安全的方法(不依赖具有pid等的环境变量,这是不安全的,因为它是变量),你应该:

扩展你的“如果”的可能性 使用开关/箱子,如果你想。

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


例如:

当您通过SSH会话源脚本时,变量“$0”(当使用source时)返回的值是-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

OR

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

我将给出一个特定于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必须驻留在源脚本中,如果你想知道被包含的文件的名称。

FWIW,在阅读了所有其他的答案后,我想出了以下解决方案:

更新:事实上,有人在另一个答案中发现了一个已经更正的错误,这也影响了我的答案。我认为这里的更新也是一个改进(如果你好奇,请参阅编辑)。

这适用于所有以#!开头的脚本。/bin/bash,但也可以由不同的shell来获取一些信息(如设置),这些信息保存在主函数之外。

根据下面的评论,这里的答案显然不适用于所有bash变体。同样不适用于/bin/sh基于bash的系统。也就是说,它失败的bash v3。在MacOS上。(目前我不知道如何解决这个问题。)

#!/bin/bash

# Function definitions (API) and shell variables (constants) go here
# (This is what might be interesting for other shells, too.)

# this main() function is only meant to be meaningful for bash
main()
{
# The script's execution part goes here
}

BASH_SOURCE=".$0" # cannot be changed in bash
test ".$0" != ".$BASH_SOURCE" || main "$@"

你可以使用以下(在我看来可读性较差)代码来代替最后两行,在其他shell中不设置BASH_SOURCE,并允许set -e在main中工作:

if ( BASH_SOURCE=".$0" && exec test ".$0" != ".$BASH_SOURCE" ); then :; else main "$@"; fi

这个脚本配方有以下属性:

If executed by bash the normal way, main is called. Please note that this does not include a call like bash -x script (where script does not contain a path), see below. If sourced by bash, main is only called, if the calling script happens to have the same name. (For example, if it sources itself or via bash -c 'someotherscript "$@"' main-script args.. where main-script must be, what test sees as $BASH_SOURCE). If sourced/executed/read/evaled by a shell other than bash, main is not called (BASH_SOURCE is always different to $0). main is not called if bash reads the script from stdin, unless you set $0 to be the empty string like so: ( exec -a '' /bin/bash ) <script If evaluated by bash with eval (eval "`cat script`" all quotes are important!) from within some other script, this calls main. If eval is run from commandline directly, this is similar to previous case, where the script is read from stdin. (BASH_SOURCE is blank, while $0 usually is /bin/bash if not forced to something completely different.) If main is not called, it does return true ($?=0). This does not rely on unexpected behavior (previously I wrote undocumented, but I found no documentation that you cannot unset nor alter BASH_SOURCE either): BASH_SOURCE is a bash reserved array. But allowing BASH_SOURCE=".$0" to change it would open a very dangerous can of worms, so my expectation is, that this must have no effect (except, perhaps, some ugly warning shows up in some future version of bash). There is no documentation that BASH_SOURCE works outside functions. However the opposite (that it only works in functions) is neither documented. The observation is, that it works (tested with bash v4.3 and v4.4, unfortunately I have no bash v3.x anymore) and that quite too many scripts would break, if $BASH_SOURCE stops working as observed. Hence my expectation is, that BASH_SOURCE stays as is for future versions of bash, too. In contrast (nice find, BTW!) consider ( return 0 ), which gives 0 if sourced and 1 if not sourced. This comes a bit unexpected not only for me , and (according to the readings there) POSIX says, that return from subshell is undefined behavior (and the return here is clearly from a subshell). Perhaps this feature eventually gets enough widespread use such that it can no more be changed, but AFAICS there is a much higher chance that some future bash version accidental changes the return behavior in that case. Unfortunately bash -x script 1 2 3 does not run main. (Compare script 1 2 3 where script has no path). Following can be used as workaround: bash -x "`which script`" 1 2 3 bash -xc '. script' "`which script`" 1 2 3 That bash script 1 2 3 does not run main can be considered a feature. Note that ( exec -a none script ) calls main (bash does not pass it's $0 to the script, for this you need to use -c as shown in the last point).

因此,除了一些极端情况外,main只在脚本以通常的方式执行时才被调用。通常这就是你想要的,特别是因为它缺乏复杂的难以理解的代码。

注意,它与Python代码非常相似: 如果__name__ == '__main__': main() 这也阻止了main的调用,除了一些角落的情况,如 你可以导入/加载脚本并强制__name__='__main__'

为什么我认为这是一个解决挑战的好方法

如果您有一些可以由多个shell提供源代码的东西,那么它必须是兼容的。然而(请阅读其他答案),由于没有(容易实现的)可移植的方法来检测来源,您应该更改规则。

通过强制脚本必须由/bin/bash执行,您可以做到这一点。

这解决了所有的情况,但在以下情况下,脚本不能直接运行:

/bin/bash未安装或无法运行(即在引导环境中) 如果你将它管道到一个shell,比如curl https://example.com/script | $ shell (注意:这只适用于最近的bash。据报道,这种配方对某些变体无效。所以一定要检查它是否适用于你的情况。)

然而,我想不出任何真正的原因,在哪里你需要它,以及能力来源完全相同的脚本并行!通常,您可以将其包装起来手动执行main。像这样:

$SHELL -c '。脚本&& main {curl https://example.com/script && echo && echo main;} | $ shell $SHELL -c“eval”“curl https://example.com/script”“&& main” echo 'eval ' ' curl https://example.com/script ' ' && main' | $SHELL

笔记

如果没有其他答案的帮助,这个答案是不可能的!甚至是错误的——最初促使我发表这篇文章的原因。 更新:由于在https://stackoverflow.com/a/28776166/490291中发现的新发现而编辑