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

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

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


当前回答

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中发现的新发现而编辑

其他回答

使用shebang行并检查它是否正在执行。

你的脚本应该有一行shebang #!/path/to/shell表示它应该在哪个shell中运行。否则,您还会遇到其他跨shell兼容性问题。

因此,您只需要通过尝试在源时不起作用的命令来检查它是否正在执行。

如。对于Bash脚本:

#!/usr/bin/env bash

if (return 0 2>/dev/null); then
    echo "Script was sourced."
fi

这个方法也适用于zsh和sh只是改变shebang行。

看了@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代替off。对于可读性(如。是源的别名):

. <(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

现在我们可以测试:

./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标志之前测试脚本:

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

或者使用预定义的变量:

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,在阅读了一些手册页后,有我的尝试:

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

在源运行中继承了一些变量,但没有什么真正相关的…

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

你甚至可以试着检查parent是什么:

把它放在你的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下。

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

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

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

编者注:这个答案的解决方案工作稳健,但只有bash。它可以简化为 (返回2 > / dev / null)。

博士TL;

尝试执行return语句。如果脚本没有来源,则会引发错误。您可以捕获该错误并按照需要进行操作。

把它放在一个文件中,并调用它,比如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中工作。

解释

如果您试图在函数之外执行return语句,或者如果脚本不是源代码,则return语句将引发错误。在shell提示符中尝试以下操作:

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

你不需要看到错误消息,所以你可以将输出重定向到dev/null:

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

现在检查逃生码。0表示OK(没有发生错误),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和Korn之间是可移植的:

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

与此类似的行或赋值语句如pathname="$_"(带有稍后的测试和操作)必须位于脚本的第一行或shebang之后的行(如果使用了shebang,则应该用于ksh,以便它在大多数情况下工作)。