如何检测脚本是否来源

问题描述 投票:166回答:16

我有一个脚本,如果它被采购,我不希望它调用exit

我想过检查是否$0 == bash但如果脚本来自另一个脚本,或者如果用户从ksh这样的不同shell中获取它,则会出现问题。

是否有可靠的方法来检测脚本是否来源?

bash ksh
16个回答
58
投票

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

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

与此类似的行或类似pathname="$_"(后面的测试和操作)的赋值必须在脚本的第一行或shebang之后的行上(如果使用的话,应该用于ksh以使其工作)在大多数情况下)。


4
投票

我想建议对Dennis' very helpful answer做一个小修正,使其稍微便于携带,我希望:

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

因为[[不被(有点肛门保持恕我直言)Debian POSIX兼容外壳,dash认可。此外,还可以在所述shell中使用引号来防止包含空格的文件名。


2
投票

$_非常脆弱。您必须在脚本中首先检查它。即便如此,也不能保证包含shell的名称(如果是源代码)或脚本的名称(如果已执行)。

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

我找到的最好方法是使用$0这样:

name="myscript.sh"

main()
{
    echo "Script was executed, running main..."
}

case "$0" in *$name)
    main "$@"
    ;;
esac

不幸的是,由于functionargzero选项的功能超出了它的名称,并且默认情况下处于启用状态,因此这种方式在zsh中无法正常使用。

为了解决这个问题,我把unsetopt functionargzero放在我的.zshenv中。


1
投票

我跟着mklement0 compact expression

这很整洁,但是我注意到在调用ksh的情况下它会失败,因为:

/bin/ksh -c ./myscript.sh

(它认为它是源代码而不是因为它执行子shell)但表达式将起作用来检测:

/bin/ksh ./myscript.sh

此外,即使表达式是紧凑的,语法也不兼容所有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

随意添加异国情调的贝壳支持:)


0
投票

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


0
投票

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

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

0
投票

直截了当:您必须评估变量“$ 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

通过SHELL:

$ bash check_source.sh 
First Parameter: check_source.sh

The script WAS NOT sourced.

通过SOURCE:

$ source check_source.sh
First Parameter: bash

The script was sourced.


使用100%可移植的方式来检测脚本是否来源是非常困难的。

关于我的经验(使用Shellscripting 7年),唯一安全的方法(不依赖于具有PID等的环境变量,由于它是VARIABLE的事实而不安全),您应该:

  • 从你的if扩展可能性
  • 如果你愿意,可以使用开关/外壳。

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


例如:

当您通过SSH会话获取脚本时,变量“$ 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

0
投票

我最终检查了[[ $_ == "$(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在运行实际脚本文件时,所以我使用type -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

146
投票

如果您的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 ..."

90
投票

针对bashkshzsh的强大解决方案,包括跨shell解决方案,以及相当强大的POSIX兼容解决方案:

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

单线跟随 - 下面解释;跨shell版本很复杂,但它应该可靠地运行:

  • bash(在3.57和4.4.19上验证) (return 0 2>/dev/null) && sourced=1 || sourced=0
  • ksh(93u +验证) [[ $(cd "$(dirname -- "$0")" && printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] && sourced=1 || sourced=0
  • zsh(在5.0.5上验证) - 务必在函数外部调用它 [[ $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 ]] && (return 0 2>/dev/null)) && sourced=1 || sourced=0
  • POSIX兼容;由于技术原因而不是完全稳健的单线(单管道)(见下图): 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 (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|dash) sourced=1;; esac fi

说明:


bash

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

注意:该技术改编自user5754163's answer,因为它证明比原始解决方案更强大,[[ $0 != "$BASH_SOURCE" ]] && sourced=1 || sourced=0 [1]

  • Bash仅允许来自函数的return语句,并且在脚本的顶级范围内,仅在脚本来源时才允许。 如果在非源脚本的顶级范围中使用return,则会发出错误消息,并且退出代码将设置为1
  • (return 0 2>/dev/null)在子shell中执行return并抑制错误消息;之后,退出代码表明脚本是否来源(0)或不是(1),它与&&||运算符一起用于相应地设置sourced变量。 使用子shell是必要的,因为在源脚本的顶级范围内执行return会退出脚本。 @Haozhun的帽子提示,通过明确使用0作为return操作数使命令更强大;他指出:根据return [N]的bash帮助:“如果省略N,则返回状态是最后一个命令的状态。”因此,如果用户shell上的最后一个命令具有非零返回值,则早期版本[仅使用return,没有操作数]会产生不正确的结果。

ksh

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

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

与bash不同,$0${.sh.file}在非源情况下不保证完全相同,因为$0可能是相对路径,而${.sh.file}总是完整路径,因此在比较之前必须将$0解析为完整路径。


zsh

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

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

警告:在命令替换中,zsh附加:cmdsubst,因此在那里测试$ZSH_EVAL_CONTEXT:file:cmdsubst$


Using POSIX features only

如果您愿意做出某些假设,那么基于知道可能正在执行脚本的shell的二进制文件名,您可以做出合理的,但不是万无一失的猜测,即您的脚本是否来源。 值得注意的是,这意味着如果您的脚本由另一个脚本提供,此方法将失败。

我在this answer中的“如何处理源调用”一节讨论了仅使用POSIX功能无法详细处理的边缘情况。

这取决于$0的标准行为,例如zsh没有表现出来。

因此,最安全的方法是将上面的强大的shell特定方法与所有剩余shell的回退解决方案相结合。

Stéphane Desneuxhis answer提供灵感的帽子的提示(将我的跨shell语句表达式转换为sh兼容的if语句并为其他shell添加处理程序)。

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
  (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|dash) sourced=1;; esac
fi

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


67
投票

阅读@ DennisWilliamson的回答后,有一些问题,见下文:

由于这个问题代表,这个答案中有另一部分关于 ...见下文。

Simple way

[ "$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代替.(因为.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

Why not to use $_ == $0 comparison

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

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

Harder: now...

由于我没有经常使用,在手册上阅读了一些内容之后,我有尝试:

#!/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,但这确保只有manualy来源的案件......

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

把它放到你的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,但这项工作仅适用于一个级别的子会话......

对不起,我在下找不到可行的方法。


30
投票

BASH_SOURCE[]答案(bash-3.0及更高版本)似乎最简单,但BASH_SOURCE[]没有记录在函数体外工作(它目前恰好工作,与手册页不一致)。

正如Wirawan Purwanto所建议的那样,最强大的方法是检查函数中的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的输出,值mainsource区分调用者的上下文。使用FUNCNAME[]可以节省捕获和解析caller输出。您需要知道或计算您的本地呼叫深度才是正确的。像从另一个函数或脚本中获取脚本的情况将导致数组(堆栈)更深。 (FUNCNAME是一个特殊的bash数组变量,它应该具有与调用堆栈相对应的连续索引,只要它永远不会是unset。)

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

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

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

return 2>/dev/null || exit

如果脚本是源,那么return将终止源脚本并返回给调用者。

如果正在执行脚本,那么return将返回错误(重定向),并且exit将正常终止脚本。如果需要,returnexit都可以使用退出代码。

遗憾的是,这在ksh中不起作用(至少不在我在这里的AT&T派生版本中),如果在函数或点源脚本之外调用,它将return视为等同于exit

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

function issourced {
    [[ ${.sh.level} -eq 2 ]]
}

issourced && echo this script is sourced

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

(您可能也对github上的this code感兴趣,它使用ksh规则函数和一些调试陷阱技巧来模拟bash FUNCNAME数组。)

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


笔记:

  • 可以创建名为“main”和“source”(overriding the builtin)的bash函数,这些名称可能出现在FUNCNAME[]中,但只要测试该数组中的最后一项,就没有歧义。
  • 我对pdksh没有一个好的答案。我能找到的最接近的东西仅适用于pdksh,其中每个脚本的源代码都会打开一个新的文件描述符(从原始脚本的10开始)。几乎肯定不是你想要依赖的东西......

19
投票

编者注:这个答案的解决方案运行稳健,但仅限于bash。它可以简化为 (return 2>/dev/null)

TL; DR

尝试执行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语句将引发错误。从shell提示符处尝试:

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

您不需要查看该错误消息,因此您可以将输出重定向到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

8
投票

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

更新:实际上somebody spottet an error in another answer也影响我的。我认为,这里的更新也是一种改进(如果你很好奇,请参阅编辑)。

这适用于所有脚本,它们以#!/bin/bash开头,但也可能由不同的shell提供。

#!/bin/bash

# Function definitions (API) and shell variables (constants) go here

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 -emain中工作:

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

此脚本配方具有以下属性:

  • 如果由bash以正常方式执行,则调用main。请注意,这不包括像bash -x script这样的调用(其中script不包含路径),请参阅下文。
  • 如果由bash提供,则只调用main,如果调用脚本恰好具有相同的名称。 (例如,如果它自己或通过bash -c 'someotherscript "$@"' main-script args..必须是main-script来源,test认为$BASH_SOURCE)。
  • 如果由eval以外的shell获取/执行/读取/ bashed,则不调用mainBASH_SOURCE总是与$0不同)。
  • 如果main从stdin读取脚本,则不调用bash,除非你将$0设置为空字符串,如下所示:( exec -a '' /bin/bash ) <script
  • 如果bash使用evaleval "`cat script`"所有引用都很重要!)从其他脚本中进行评估,则会调用main。如果eval直接从命令行运行,这与之前的情况类似,其中从stdin读取脚本。 (BASH_SOURCE是空白的,而$0通常是/bin/bash,如果没有强迫完全不同的东西。)
  • 如果没有调用main,它确实返回true$?=0)。
  • 这不依赖于意外的行为(以前我写的没有记录,但我发现没有文件,你不能unset也不改变BASH_SOURCE): BASH_SOURCE is a bash reserved array。但是允许BASH_SOURCE=".$0"改变它会打开一个非常危险的蠕虫,所以我的期望是,这一定无效(除了,或许,在某些未来版本的bash中会出现一些丑陋的警告)。 BASH_SOURCE没有文档在函数之外工作。然而,相反(它只在函数中起作用)没有记录。观察是,它运行(用bash v4.3和v4.4进行测试,不幸的是我没有bash v3.x了)并且如果$BASH_SOURCE停止工作,那么太多的脚本会破坏。因此,我的期望是,BASH_SOURCE也保留了未来版本的bash。 相比之下(很好的发现,BTW!)考虑( return 0 ),如果没有来源,它会给01This comes a bit unexpected not only for me ,和(根据那里的读数)POSIX说,来自子壳的return是未定义的行为(这里的return显然来自子壳)。也许这个功能最终得到了足够的广泛使用,以至于它不能再被改变,但AFAICS有一个更高的机会,在这种情况下,某些未来的bash版本会意外地改变返回行为。
  • 不幸的是bash -x script 1 2 3没有运行main。 (比较script 1 2 3,其中script没有路径)。以下可用作解决方法: bash -x "`which script`" 1 2 3 bash -xc '. script' "`which script`" 1 2 3bash script 1 2 3没有运行main可以被认为是一个功能。
  • 请注意,( exec -a none script )调用mainbash没有将它的$0传递给脚本,为此你需要使用-c,如最后一点所示)。

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

请注意,它与Python代码非常相似:

if __name__ == '__main__': main()

这也阻止调用main,除了一些极端情况,因为你可以导入/加载脚本并强制执行__name__='__main__'

Why I think this is a good general way to solve the challenge

如果您有可以由多个shell获取的内容,则必须兼容。但是(阅读其他答案),因为没有(易于实现)可移植的方式来检测sourceing,你应该改变规则。

通过强制执行脚本必须由/bin/bash执行,您完全是这样做的。

这解决了所有情况,但在这种情况下脚本无法直接运行:

  • /bin/bash没有安装或功能不正常(即在启动环境中使用E.)
  • 如果你把它管道到像curl https://example.com/script | $SHELL这样的shell

但是,我无法考虑您需要它的任何真正原因,也无法并行获取完全相同的脚本!通常你可以包装它来手动执行main。像那样:

  • $SHELL -c '. script && 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

Notes


5
投票

我将给出一个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的内省设施,即内置变量FUNCNAMEBASH_SOURCE;在bash手册页中查看他们的文档。

只有两点需要注意:

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

2)如果要查找所包含文件的名称,am_I_called函数必须位于源脚本中。


4
投票

这将在稍后的脚本中工作,并且不依赖于_变量:

## 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
© www.soinside.com 2019 - 2024. All rights reserved.