如何检测脚本是否来源

时间:2010-04-21 13:28:23

标签: bash ksh

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

我想过检查是否$0 == bash但如果脚本来自其他脚本,或者用户从其他shell(如ksh)获取脚本,则会出现问题。

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

19 个答案:

答案 0 :(得分:139)

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

答案 1 :(得分:86)

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

  • 给出的版本号是验证功能的版本号 - 可能这些解决方案也适用于早期版本 - 反馈欢迎

  • 仅使用 POSIX功能(例如dash,在Ubuntu上充当/bin/sh),没有健壮< / em> way 确定脚本是否来源 - 请参阅下面的最佳近似

单行跟随 - 解释如下;跨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
    
  • cross-shell(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语句,并且在脚本的顶级作用域中,仅当脚本 sourced 时才允许使用return语句。

    • 如果在非源脚本的顶级作用域中使用1,则会发出错误消息,并且退出代码设置为(return 0 2>/dev/null)
  • return子shell 中执行0并禁止显示错误消息;然后退出代码指示脚本是否来源(1)或不是&&),它与||sourced运算符一起用于设置{{1相应的变量。

    • 使用子shell是必要的,因为在源脚本的顶级范围内执行return会退出脚本。
    • 提示@Haozhun,通过明确使用0作为return操作数,使命令更加健壮;他指出:return [N]的每个bash帮助:&#34;如果省略N,则返回状态是最后一个命令的状态。&#34;因此,早期版本[仅使用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 以<{1}}结束

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


仅使用POSIX功能

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

部分&#34;如何处理来源的调用&#34;我在this answer中讨论了仅使用POSIX功能无法处理的边缘情况。

依赖于:file:cmdsubst$的标准行为,$0,例如展示。

因此,最安全的方法是将上面强大的,特定于shell的方法与后备解决方案 结合起来用于所有剩余的shell。

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

if

[1] user1902689发现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 通过传递[[ $0 != "$BASH_SOURCE" ]] 中的脚本时会产生误报> {i} filename $PATH二进制文件;例如,bash,因为bash my-script只是$0,而my-script完整路径。虽然您通常不会使用此技术来调用$BASH_SOURCE中的脚本 - 您只需直接调用 $PATH) - 它对于调试my-script结合使用时有用。

答案 2 :(得分:66)

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

由于此问题代表 ,因此此答案中有另一部分涉及 ...请参阅下文。

简单方式

[ "$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

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

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

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

更难:现在......

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

#!/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,但这只能确保手动案件......

您甚至可以尝试检查父母的

将其放入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,但这只适用于一个级别的子会话......

抱歉,在下,我无法找到可行的方法。

答案 3 :(得分:54)

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

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

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

答案 4 :(得分: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]}代替数组中的最后一项。感谢丹尼斯威廉姆森在下面的评论,改进和简化。)

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

return 2>/dev/null || exit

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

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

可悲的是,这在ksh中不起作用(至少在我在这里的AT&amp; T派生版本中没有),如果在外面调用它会将return视为等同于exit函数或点源脚本。

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

.sh.level

这不如bash版本强大,您必须在顶级或已知函数深度的文件中调用function issourced { [[ ${.sh.level} -eq 2 ]] } issourced && echo this script is sourced

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

这里的规范答案:http://mywiki.wooledge.org/BashFAQ/109还提供FUNCNAME作为shell状态的另一个指标(尽管不完美)。


注意:

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

答案 5 :(得分:18)

编者注:这个答案的解决方案运行稳健,但仅限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

答案 6 :(得分:7)

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-scripttest看作$BASH_SOURCE)。

  • 如果eval以外的来源/执行/阅读/ bashmain未被调用(BASH_SOURCE总是与{{$0不同1}})。

  • 如果main从stdin读取脚本,则不会调用
  • bash,除非您将$0设置为空字符串,如下所示:( exec -a '' /bin/bash ) <script

  • 如果bash使用evaleval "`cat script`" 所有引号都很重要!)从其他脚本中进行评估,则会调用{{1} }}。如果直接从命令行运行main,则类似于先前的情况,其中从stdin读取脚本。 (eval为空,而BASH_SOURCE通常为$0,如果没有强制完全不同的话。{/ p>

  • 如果未调用/bin/bash,则会返回maintrue)。

  • 这不依赖于意外行为(之前我写的是未记录的行为,但我发现没有文档说明你不能$?=0也不能改变unset

    • BASH_SOURCE is a bash reserved array。但允许BASH_SOURCE更改它会打开一个非常危险的蠕虫病毒,所以我的期望是,这一定不会产生任何影响(可能会有一些丑陋的警告出现在BASH_SOURCE=".$0"的某个未来版本中)。
    • 没有bash在函数外部工作的文档。然而,相反(它只在函数中起作用)没有记录。观察是,它工作(用BASH_SOURCE v4.3和v4.4测试,不幸的是我不再有bash v3.x)并且如果{{1停止工作。因此,我的期望是bash保留$BASH_SOURCE的未来版本。
    • 相比之下(很好的发现,顺便说一句!)考虑BASH_SOURCE,如果来源则提供bash,如果没有来源则提供( return 0 )This comes a bit unexpected not only for me ,并且(根据那里的读数)POSIX说,来自subshel​​l的0是未定义的行为(这里的1显然来自子shell)。也许这个功能最终得到了足够广泛的使用,以至于它不能再被改变,但是AFAICS在将来return版本意外改变返回行为的可能性要大得多。
  • 不幸的是return无法运行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 3可被视为功能。
  • 请注意,bash script 1 2 3次调用main( exec -a none script )未将其main传递给脚本,为此您需要使用{{} 1}}如最后一点所示。)

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

  

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

$0
     

除了某些极端情况外,还可以阻止调用-c   您可以导入/加载脚本并强制执行main

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

如果您有可以由多个shell获取的内容,则必须兼容。但是(阅读其他答案),因为没有(易于实现)便携式方法来检测if __name__ == '__main__': main() ,您应该更改规则

通过强制执行脚本必须由main执行,您才能完全执行此操作。

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

  • __name__='__main__'未安装或功能失效(即在引导环境中为E.)
  • 如果您将其管道到source
  • 中的shell

然而,我无法想到你需要它的任何真正原因,以及能够并行获取完全相同的脚本的能力!通常你可以包装它来手动执行/bin/bash。像那样:

  • /bin/bash
  • curl https://example.com/script | $SHELL
  • main
  • $SHELL -c '. script && main'

注释

答案 7 :(得分:5)

我将给出一个BASH特定的答案。 Korn shell,对不起。假设您的脚本名称为include2.sh;然后在名为include2.sh的{​​{1}}中创建内部的函数。这是我的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手册页中查看他们的文档。

只有两点需要注意:

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

2)如果要查找所包含文件的名称,函数${FUNCNAME[1]} 必须驻留在源脚本中。

答案 8 :(得分:4)

我想建议对Dennis' very helpful answer进行一次小修正,以使其更具可移植性,我希望:

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

因为(有点肛门保留恕我直言)Debian POSIX兼容 shell [[无法识别dash。此外,可能需要引号来防止包含空格的文件名,再次在所述shell中。

答案 9 :(得分: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

答案 10 :(得分: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

答案 11 :(得分: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

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

答案 12 :(得分:0)

不完全是 OP 想要的,但我经常发现自己需要获取脚本以加载其功能(即作为库)。例如,用于基准测试或测试目的。

这是一个适用于所有 shell(包括 POSIX)的设计

  • 将所有顶级操作封装在一个 run_main() 函数中
  • 让您的源脚本检查初始 --no-run 参数,该参数执行任何操作; 没有 --no-run,它可以调用run_main
  • 使用
  • “源”脚本
set -- --no-run "$@"
. script.sh
shift

简单的 . / source 的问题是无法将参数可移植地传递给脚本。 POSIX shell 会忽略 . 的参数并无论如何传递调用者的 "$@"

答案 13 :(得分:0)

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

答案 14 :(得分:0)

解决此问题的方法不是编写需要知道此类事情才能正确运行的代码。这样做的方法是将代码放入一个函数中,而不是放入需要获取源的脚本的主线中。

函数内的代码可以是 return 0return 1。这仅终止函数,以便控制返回到调用该函数的任何地方。

无论是从源脚本的主线、顶级脚本的主线还是其他函数调用该函数,这都有效。

使用采购引入“库”脚本,这些脚本只定义函数和变量,但实际上并不执行任何其他顶级命令:

. path/to/lib.sh # defines libfunction
libfunction arg

否则:

path/to/script.sh arg # call script as a child process

而不是:

. path/to/script.sh arg  # shell programming anti-pattern

答案 15 :(得分:0)

这是从其他一些答案中衍生出来的,涉及“通用”跨壳支持。诚然,这与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只是确保可以将该机制扩展到子进程中。

答案 16 :(得分: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,所以我使用{{1 }}以显示bash的完整路径。

测试:

type -p "$0"

答案 17 :(得分: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

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

答案 18 :(得分:0)

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

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