bash 如何检测脚本是否正在获取

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2683279/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 19:13:03  来源:igfitidea点击:

How to detect if a script is being sourced

bashksh

提问by brianegge

I have a script where I do not want it to call exitif it's being sourced.

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

I thought of checking if $0 == bashbut this has problems if the script is sourced from another script, or if the user sources it from a different shell like ksh.

我想检查如果$0 == bash脚本来自另一个脚本,或者用户是否从不同的外壳程序(如ksh.

Is there a reliable way of detecting if a script is being sourced?

是否有一种可靠的方法来检测脚本是否正在获取?

采纳答案by Paused until further notice.

This seems to be portable between Bash and Korn:

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

[[ $_ != 
# man bash | less -p BASH_SOURCE
#[[ ${BASH_VERSINFO[0]} -le 2 ]] && echo 'No BASH_SOURCE array variable' && exit 1

[[ "${BASH_SOURCE[0]}" != "
(return 0 2>/dev/null) && sourced=1 || sourced=0
" ]] && echo "script ${BASH_SOURCE[0]} is being sourced ..."
]] && echo "Script is being sourced" || echo "Script is a subshell"

A line similar to this or an assignment like pathname="$_"(with a later test and action) must be on the first line of the script or on the line after the shebang (which, if used, should be for ksh in order for it to work under the most circumstances).

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

回答by barroyo

If your Bash version knows about the BASH_SOURCE array variable, try something like:

如果您的 Bash 版本知道 BASH_SOURCE 数组变量,请尝试以下操作:

[[ $(cd "$(dirname -- "
[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0
")" && printf '%s' "${PWD%/}/")$(basename -- "
([[ -n $ZSH_EVAL_CONTEXT && $ZSH_EVAL_CONTEXT =~ :file$ ]] || 
 [[ -n $KSH_VERSION && $(cd "$(dirname -- "
sourced=0
if [ -n "$ZSH_EVAL_CONTEXT" ]; then 
  case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
elif [ -n "$KSH_VERSION" ]; then
  [ "$(cd $(dirname -- 
(return 0 2>/dev/null) && sourced=1 || sourced=0
) && pwd -P)/$(basename --
[[ $(cd "$(dirname -- "
[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0
")" && printf '%s' "${PWD%/}/")$(basename -- "
([[ -n $ZSH_EVAL_CONTEXT && $ZSH_EVAL_CONTEXT =~ :file$ ]] || 
 [[ -n $KSH_VERSION && $(cd "$(dirname -- "
sourced=0
if [ -n "$ZSH_EVAL_CONTEXT" ]; then 
  case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
elif [ -n "$KSH_VERSION" ]; then
  [ "$(cd $(dirname -- 
(return 0 2>/dev/null) && sourced=1 || sourced=0
) && pwd -P)/$(basename --
[[ \
   $(cd "$(dirname -- "
[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0
")" && printf '%s' "${PWD%/}/")$(basename -- "
sourced=0
if [ -n "$ZSH_EVAL_CONTEXT" ]; then 
  case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
elif [ -n "$KSH_VERSION" ]; then
  [ "$(cd $(dirname -- 
[ "
source <(echo $'#!/bin/bash
           [ "
. <(echo $'#!/bin/bash
           [ "
echo $$
29301
" = "$BASH_SOURCE" ] && v=own || v=sourced; echo "process $$ is $v (
#!/bin/bash

# As $_ could be used only once, uncomment one of two following lines

#printf '_="%s", 0="%s" and BASH_SOURCE="%s"\n' "$_" "
cat >testscript   
chmod +x testscript
" "$BASH_SOURCE" [[ "$_" != "
./testscript 
proc: 25758[ppid:24890] is own (DW purpose: subshell)
" ]] && DW_PURPOSE=sourced || DW_PURPOSE=subshell [ "
. ./testscript 
proc: 24890[ppid:24885] is sourced (DW purpose: sourced)

source ./testscript 
proc: 24890[ppid:24885] is sourced (DW purpose: sourced)
" = "$BASH_SOURCE" ] && BASH_KIND_ENV=own || BASH_KIND_ENV=sourced; echo "proc: $$[ppid:$PPID] is $BASH_KIND_ENV (DW purpose: $DW_PURPOSE)"
, $BASH_SOURCE)" ') process 29301 is sourced (bash, /dev/fd/63)
" = "$BASH_SOURCE" ] && v=own || v=sourced; echo "process $$ is $v (
bash ./testscript 
proc: 25776[ppid:24890] is own (DW purpose: sourced)
, $BASH_SOURCE)" ') process 29301 is sourced (bash, /dev/fd/63) bash <(echo $'#!/bin/bash [ "
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)
" = "$BASH_SOURCE" ] && v=own || v=sourced; echo "process $$ is $v (
./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
, $BASH_SOURCE)" ') process 16229 is own (/dev/fd/63, /dev/fd/63)
" = "$BASH_SOURCE" ]
) && pwd -P)/$(basename --
#!/bin/ksh

set >/tmp/ksh-$$.log
)" != "$(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
cat >testfile.ksh
chmod +x testfile.ksh
for known shell binary filenames # Detects `sh` and `dash`; add additional shell filenames as needed. case ${0##*/} in sh|dash) sourced=1;; esac fi
") != \ "${.sh.file}" \ ]] && sourced=1 || sourced=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
./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
for known shell binary filenames # Detects `sh` and `dash`; add additional shell filenames as needed. case ${0##*/} in sh|dash) sourced=1;; esac fi
")" && printf '%s' "${PWD%/}/")$(basename -- "
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
") != "${.sh.file}" ]] || [[ -n $BASH_VERSION ]] && (return 0 2>/dev/null)) && sourced=1 || sourced=0
") != "${.sh.file}" ]] && sourced=1 || sourced=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
ps $PPID
for known shell binary filenames # Detects `sh` and `dash`; add additional shell filenames as needed. case ${0##*/} in sh|dash) sourced=1;; esac fi
")" && printf '%s' "${PWD%/}/")$(basename -- "
./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
") != "${.sh.file}" ]] || [[ -n $BASH_VERSION ]] && (return 0 2>/dev/null)) && sourced=1 || sourced=0
") != "${.sh.file}" ]] && sourced=1 || sourced=0

回答by mklement0

Robust solutions for bash, ksh, zsh, including a cross-shellone, plus a reasonably robust POSIX-compliant solution:

bash, ksh, 的稳健解决方案zsh,包括跨壳解决方案,以及合理稳健的符合 POSIX 的解决方案

  • Version numbers given are the ones on which functionality was verified- likely, these solutions work on much earlier versions, too - feedback welcome.

  • Using POSIX features only(such as in dash, which acts as /bin/shon Ubuntu), there is no robustwayto determine if a script is being sourced - see below for the best approximation.

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

  • 使用POSIX 功能(例如 in dash,它/bin/sh在 Ubuntu上起作用 ),没有可靠的方法来确定是否正在获取脚本 - 请参阅下面的最佳近似值

One-linersfollow - explanation below; the cross-shell version is complex, but it should work robustly:

单行如下 - 解释如下;跨壳版本很复杂,但它应该可以很好地工作:

  • bash(verified on 3.57 and 4.4.19)

    function mycheck() { declare -p FUNCNAME; }
    mycheck
    
  • ksh(verified on 93u+)

    $ bash sourcetest.sh
    declare -a FUNCNAME='([0]="mycheck" [1]="main")'
    $ . sourcetest.sh
    declare -a FUNCNAME='([0]="mycheck" [1]="source")'
    
  • zsh(verified on 5.0.5) - be sure to call this outside of a function

    function issourced() {
        [[ ${FUNCNAME[@]: -1} == "source" ]]
    }
    
  • cross-shell (bash, ksh, zsh)

    return 2>/dev/null || exit
    
  • POSIX-compliant; not a one-liner(single pipeline) for technical reasons and not fullyrobust(see bottom):

    function issourced {
        [[ ${.sh.level} -eq 2 ]]
    }
    
    issourced && echo this script is sourced
    
  • bash(在 3.57 和 4.4.19 上验证)

    #!/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
    
  • ksh(在 93u+ 上验证)

    shell-prompt> sh test.sh
    output: This script is not sourced.
    
  • zsh(在 5.0.5 上验证) - 一定要在函数之外调用它

    shell-prompt> source test.sh
    output: This script is sourced.
    
  • 跨外壳(bash、ksh、zsh)

    shell-prompt> return
    output: ...can only `return` from a function or sourced script
    
  • 符合 POSIX 标准;不是一个班轮(单管)因技术原因而无法完全健壮(参见下图):

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


Explanation:

解释:



bash

猛击

shell-prompt> echo $?
output: 1

Note: The technique was adapted from user5754163's answer, as it turned out to be more robust than the original solution, [[ $0 != "$BASH_SOURCE" ]] && sourced=1 || sourced=0[1]

注意:该技术改编自user5754163 的回答,结果证明它比原始解决方案更健壮,[[ $0 != "$BASH_SOURCE" ]] && sourced=1 || sourced=0[1]

  • Bash allows returnstatements only from functions and, in a script's top-level scope, only if the script is sourced.

    • If returnis used in the top-level scope of a non-sourcedscript, an error message is emitted, and the exit code is set to 1.
  • (return 0 2>/dev/null)executes returnin a subshelland suppresses the error message; afterwards the exit code indicates whether the script was sourced (0) or not (1), which is used with the &&and ||operators to set the sourcedvariable accordingly.

    • Use of a subshell is necessary, because executing returnin the top-level scope of a sourced script would exit the script.
    • Tip of the hat to @Haozhun, who made the command more robust by explicitly using 0as the returnoperand; he notes: per bash help of return [N]: "If N is omitted, the return status is that of the last command." As a result, the earlier version [which used just return, without an operand] produces incorrect result if the last command on the user's shell has a non-zero return value.
  • Bashreturn仅允许来自函数的语句,并且在脚本的顶级范围内,仅当脚本来自.

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

    • 使用子 shell 是必要的,因为return在源脚本的顶级范围内执行会退出脚本。
    • @Haozhun 致敬,他通过显式0用作return操作数使命令更加健壮;他指出:根据 bash 帮助return [N]:“如果省略 N,则返回状态是最后一个命令的状态。” 因此,return如果用户 shell 上的最后一个命令具有非零返回值,则早期版本 [仅使用,不使用操作数] 会产生不正确的结果。


ksh

克什

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

Special variable ${.sh.file}is somewhat analogous to $BASH_SOURCE; note that ${.sh.file}causes a syntax errorin bash, zsh, and dash, so be sure to execute it conditionallyin multi-shell scripts.

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

Unlike in bash, $0and ${.sh.file}are NOT guaranteed to be exactlyidentical in the non-sourced case, as $0may be a relativepath, while ${.sh.file}is always a fullpath, so $0must be resolved to a full path before comparing.

不像在bash,$0${.sh.file}不能保证是准确的非采购情况相同,因为$0可能是一个相对路径,而${.sh.file}始终是一个完整的路径,所以$0必须解决一个完整路径比较之前。



zsh

zsh

shell-prompt> echo $?
output: 1

$ZSH_EVAL_CONTEXTcontains information about the evaluation context - call this outside of a function. Inside a sourced script['s top-level scope], $ZSH_EVAL_CONTEXTendswith :file.

$ZSH_EVAL_CONTEXT包含有关评估上下文的信息 - 在函数外部调用它。内的执行的脚本['的顶层范围],$ZSH_EVAL_CONTEXT端部带有:file

Caveat: Inside a command substitution, zsh appends :cmdsubst, so test $ZSH_EVAL_CONTEXTfor :file:cmdsubst$there.

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



Using POSIX features only

仅使用 POSIX 功能

If you're willing to make certain assumptions, you can make a reasonable, but not fool-proof guessas to whether your script is being sourced, based on knowing the binary filenamesof the shells that may be executing your script.
Notably, this means that this approach fails if your script is being sourced by another script.

如果您愿意做出某些假设,您可以根据了解可能正在执行您的脚本的 shell的二进制文件名,对您的脚本是否来自其来源做出合理但并非万无一失的猜测。 值得注意的是,这意味着如果您的脚本来自另一个脚本,则此方法将失败。

The section "How to handle sourced invocations" in this answerof mine discusses the edge cases that cannotbe handled with POSIX features only in detail.

我的这个答案中的“如何处理源调用”部分仅详细讨论了无法使用 POSIX 功能处理的边缘情况。

This relies on the standard behavior of $0, which zsh, for instance does notexhibit.

依赖于标准的行为$0,其中zsh,例如没有表现。

Thus, the safest approach is to combine the robust, shell-specific methods above with a fallback solutionfor all remaining shells.

因此,最安全的方法是将上述稳健的、特定于 shell 的方法与所有剩余 shell后备解决方案相结合

Tip of the hat to Stéphane Desneuxand his answerfor the inspiration (transforming my cross-shell statement expression into a sh-compatible ifstatement and adding a handler for other shells).

Stéphane Desneux 致敬,以及对灵感的回答(将我的跨壳语句表达式转换为sh-compatibleif语句并为其他壳添加处理程序)。

#!/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=".
if ( BASH_SOURCE=".
if __name__ == '__main__': main()
" && exec test ".
if __name__ == '__main__': main()
" != ".$BASH_SOURCE" ); then :; else main "$@"; fi
" # cannot be changed in bash test ".
## Check to make sure it is not sourced:
Prog=myscript.sh
if [ $(basename 
[ $(basename 
am_I_sourced()
{
  if [ "${FUNCNAME[1]}" = source ]; then
    if [ "" = -v ]; then
      echo "I am being sourced, this filename is ${BASH_SOURCE[0]} and my caller script/shell name was 
~/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
" fi return 0 else if [ "" = -v ]; then echo "I am not being sourced, my script/shell name was
[ "$_" != "##代码##" ] && echo "Script is being sourced" || echo "Script is a subshell"
" fi return 1 fi } if am_I_sourced -v; then echo "Do something with sourced script" else echo "Do something with executed script" fi
) = $Prog ] && exit
) = $Prog ]; then exit 1 # not sourced fi
" != ".$BASH_SOURCE" || main "$@"


[1] user1902689discovered that [[ $0 != "$BASH_SOURCE" ]]yields a false positive when you execute a script located in the $PATHby passing its mere filenameto the bashbinary; e.g., bash my-script, because $0is then just my-script, whereas $BASH_SOURCEis the full path. While you normally wouldn't use this technique to invoke scripts in the $PATH- you'd just invoke them directly(my-script) - it ishelpful when combined with -xfor debugging.

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

回答by F. Hauri

After reading @DennisWilliamson's answer, there are some issues, see below:

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

As this question stand for kshandbash, there is another part in this answer concerning ksh... see below.

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

Simple bashway

简单的bash方式

##代码##

Let's try (on the fly because that bash could ;-):

让我们尝试一下(因为那个 bash 可以 ;-):

##代码##

I use sourceinstead off .for readability (as .is an alias to source):

我使用sourceoff.来提高可读性(作为.的别名source):

##代码##

Note that process number don't change while process stay sourced:

请注意,流程编号不会更改,而流程保持来源

##代码##

Why not to use $_ == $0comparison

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

For ensuring many case, I begin to write a truescript:

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

##代码##

Copy this to a file called testscript:

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

##代码##

Now we could test:

现在我们可以测试:

##代码##

That's ok.

没关系。

##代码##

That's ok.

没关系。

But,for testing a script before adding -xflag:

但是,在添加-x标志之前测试脚本:

##代码##

Or to use pre-defined variables:

或者使用预定义的变量:

##代码##

This won't work anymore.

这将不再起作用。

Moving comment from 5th line to 6th would give more readable answer:

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

##代码##

Harder: kshnow...

更难:ksh现在...

As I don't use ksha lot, after some read on the man page, there is my tries:

由于我不经常使用ksh,因此在手册页上阅读了一些内容后,我尝试了以下方法:

##代码##

Copy this in a testfile.ksh:

将此复制到一个testfile.ksh

##代码##

Than run it two time:

比运行它两次:

##代码##

and see:

并看到:

##代码##

There is some variable herited in a sourcedrun, but nothing really related...

运行中继承了一些变量,但没有真正相关的......

You could even check that $SECONDSis close to 0.000, but that's ensure only manualy sourcedcases...

您甚至可以检查它$SECONDS是否接近0.000,但这确保只有手动来源的案例......

You even could try to check for what'sparent is:

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

Place this into your testfile.ksh:

将其放入您的testfile.ksh

##代码##

Than:

比:

##代码##

or ps ho cmd $PPID, but this work only for one level of subsessions...

或者ps ho cmd $PPID,但这项工作仅适用于一级子会话......

Sorry, I couldn't find a reliable way of doing that, under ksh.

抱歉,我在ksh下找不到可靠的方法。

回答by mr.spuratic

The BASH_SOURCE[]answer (bash-3.0 and later) seems simplest, though BASH_SOURCE[]is not documented to work outside a function body(it currently happens to work, in disagreement with the man page).

BASH_SOURCE[]答案(bash的-3.0和更高版本)似乎最简单的,虽然BASH_SOURCE[]未记录到工作在函数体外部(它目前发生在工作中,不同意该男子页)。

The most robust way, as suggested by Wirawan Purwanto, is to check FUNCNAME[1]within a function:

正如 Wirawan Purwanto 所建议的,最可靠的方法是FUNCNAME[1]在函数内进行检查:

##代码##

Then:

然后:

##代码##

This is the equivalent to checking the output of caller, the values mainand sourcedistinguish the caller's context. Using FUNCNAME[]saves you capturing and parsing calleroutput. You need to know or calculate your local call depth to be correct though. Cases like a script being sourced from within another function or script will cause the array (stack) to be deeper. (FUNCNAMEis a special bash array variable, it should have contiguous indexes corresponding to the call stack, as long as it is never unset.)

这相当于检查 的输出caller、值mainsource区分调用者的上下文。使用FUNCNAME[]可以保存您捕获和解析caller输出。不过,您需要知道或计算本地呼叫深度才能正确。像脚本来自另一个函数或脚本之类的情况会导致数组(堆栈)更深。(FUNCNAME是一个特殊的 bash 数组变量,它应该有对应于调用堆栈的连续索引,只要它从不unset。)

##代码##

(In bash-4.2 and later you can use the simpler form ${FUNCNAME[-1]}instead for the last item in the array. Improved and simplified thanks to Dennis Williamson's comment below.)

(在 bash-4.2 及更高版本中,您可以使用更简单的形式${FUNCNAME[-1]}代替数组中的最后一项。由于丹尼斯威廉姆森的评论在下面进行了改进和简化。)

However, your problem as stated is "I have a script where I do not want it to call 'exit' if it's being sourced". The common bashidiom for this situation is:

但是,如上所述,您的问题是“我有一个脚本,如果它被采购,我不希望它调用'退出'”。bash这种情况的常见习语是:

##代码##

If the script is being sourced then returnwill terminate the sourced script and return to the caller.

如果正在获取return脚本,则将终止获取的脚本并返回给调用者。

If the script is being executed, then returnwill return an error (redirected), and exitwill terminate the script as normal. Both returnand exitcan take an exit code, if required.

如果脚本正在执行,return则将返回错误(重定向),并将exit正常终止脚本。双方returnexit可以采取的退出代码,如果需要的话。

Sadly, this doesn't work in ksh(at least not in the AT&T derived version I have here), it treats returnas equivalent to exitif invoked outside a function or dot-sourced script.

可悲的是,这在ksh(至少不是我这里的 AT&T 派生版本中)不起作用,它被视为return等效于exit在函数或点源脚本之外调用。

Updated: What you cando in contemporary versions of kshis to check the special variable .sh.levelwhich is set to the function call depth. For an invoked script this will initially be unset, for a dot-sourced script it will be set to 1.

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

##代码##

This is not quite as robust as the bash version, you must invoke issourced()in the file you are testing from at the top level or at a known function depth.

这不像 bash 版本那么健壮,您必须issourced()从顶层或已知函数深度调用您正在测试的文件。

(You may also be interested in this codeon github which uses a kshdiscipline function and some debug trap trickery to emulate the bash FUNCNAMEarray.)

(您可能也对 github 上的这段代码感兴趣,它使用ksh纪律函数和一些调试陷阱技巧来模拟 bashFUNCNAME数组。)

The canonical answer here: http://mywiki.wooledge.org/BashFAQ/109also offers $-as another indicator (though imperfect) of the shell state.

此处的规范答案:http: //mywiki.wooledge.org/BashFAQ/109还提供$-了外壳状态的另一个指标(尽管不完美)。



Notes:

笔记:

  • it is possible to create bash functions named "main" and "source" (overriding the builtin), these names may appear in FUNCNAME[]but as long as only the last item in that array is tested there is no ambiguity.
  • I don't have a good answer for pdksh. The closest thing I can find applies only to pdksh, where each sourcing of a script opens a new file descriptor (starting with 10 for the original script). Almost certainly not something you want to rely on...
  • 可以创建名为“main”和“source”(覆盖内置函数)的 bash 函数,这些名称可能会出现在,FUNCNAME[]但只要只测试该数组中的最后一项,就没有歧义。
  • 我没有很好的答案pdksh。我能找到的最接近的东西仅适用于pdksh,其中每个脚本来源都会打开一个新的文件描述符(原始脚本从 10 开始)。几乎可以肯定不是你想要依赖的东西......

回答by user5754163

Editor's note: This answer's solution works robustly, but is bash-only. It can be streamlined to
(return 2>/dev/null).

编者注:此答案的解决方案运行良好,但bash仅限于。它可以精简为
(return 2>/dev/null).

TL;DR

TL; 博士

Try to execute a returnstatement. If the script isn't sourced, that will raise an error. You can catch that error and proceed as you need.

尝试执行一条return语句。如果脚本不是源代码,则会引发错误。您可以捕获该错误并根据需要继续操作。

Put this in a file and call it, say, test.sh:

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

##代码##

Execute it directly:

直接执行:

##代码##

Source it:

来源:

##代码##

For me, this works in zsh and bash.

对我来说,这适用于 zsh 和 bash。

Explanation

解释

The returnstatement will raise an error if you try to execute it outside of a function or if the script is not sourced. Try this from a shell prompt:

return如果您尝试在函数之外执行该语句或脚本未找到源代码,则该语句将引发错误。在 shell 提示下试试这个:

##代码##

You don't need to see that error message, so you can redirect the output to dev/null:

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

##代码##

Now check the exit code. 0 means OK (no errors occurred), 1 means an error occurred:

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

##代码##

You also want to execute the returnstatement inside of a sub-shell. When the returnstatement runs it . . . well . . . returns. If you execute it in a sub-shell, it will return out of that sub-shell, rather than returning out of your script. To execute in the sub-shell, wrap it in $(...):

您还想return在子 shell 内执行语句。当return语句运行它。. . 好 。. . 返回。如果您在子 shell 中执行它,它将从该子 shell 中返回,而不是从您的脚本中返回。要在子 shell 中执行,请将其包装在$(...)

##代码##

Now, you can see the exit code of the sub-shell, which should be 1, because an error was raised inside the sub-shell:

现在,您可以看到子外壳的退出代码,应该是 1,因为在子外壳内部引发了错误:

##代码##

回答by Tino

FWIW, after reading all of the other answers, I came up with following solution for me:

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

Update: Actually, somebody spotted a since-corrected error in another answerwhich affected mine, too. I think the update here also is an improvement (see edits if you are curious).

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

This works for all scripts, which start with #!/bin/bashbut might be sourced by different shells as well to learn some information (like settings) which is are kept outside the mainfunction.

这适用于所有脚本,这些脚本开始#!/bin/bash但也可能来自不同的 shell,以了解一些保存在main函数之外的信息(如设置)。

According to the comments below, this answer here apparently does not work for all bashvariants. Also not for systems, where /bin/shis based on bash. I. E. it fails for bashv3.x on MacOS. (Currenty I do not know how to solve this.)

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

##代码##

Instead of the last 2 lines you can use following (in my opinion less readable) code to not set BASH_SOURCEin other shells and allow set -eto work in main:

您可以使用以下(在我看来不太可读)代码而不是最后 2 行代码来不在BASH_SOURCE其他 shell 中设置并允许set -e在其中工作main

##代码##

This script-recipe has following properties:

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

  • If executed by bashthe normal way, mainis called. Please note that this does not include a call like bash -x script(where scriptdoes not contain a path), see below.

  • If sourced by bash, mainis 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-scriptmust be, what testsees as $BASH_SOURCE).

  • If sourced/executed/read/evaled by a shell other than bash, mainis not called (BASH_SOURCEis always different to $0).

  • mainis not called if bashreads the script from stdin, unless you set $0to be the empty string like so: ( exec -a '' /bin/bash ) <script

  • If evaluated by bashwith eval(eval "`cat script`"all quotes are important!) from within some other script, this calls main. If evalis run from commandline directly, this is similar to previous case, where the script is read from stdin. (BASH_SOURCEis blank, while $0usually is /bin/bashif not forced to something completely different.)

  • If mainis 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 unsetnor alter BASH_SOURCEeither):

    • BASH_SOURCEis 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_SOURCEworks outside functions. However the opposite (that it only works in functions) is neither documented. The observation is, that it works (tested with bashv4.3 and v4.4, unfortunately I have no bashv3.x anymore) and that quite too many scripts would break, if $BASH_SOURCEstops working as observed. Hence my expectation is, that BASH_SOURCEstays as is for future versions of bash, too.
    • In contrast (nice find, BTW!) consider ( return 0 ), which gives 0if sourced and 1if not sourced. This comes a bit unexpected not only for me , and (according to the readings there) POSIX says, that returnfrom subshell is undefined behavior (and the returnhere 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 bashversion accidental changes the return behavior in that case.
  • Unfortunately bash -x script 1 2 3does not run main.(Compare script 1 2 3where scripthas 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 3does not run maincan be considered a feature.
  • Note that ( exec -a none script )calls main(bashdoes not pass it's $0to the script, for this you need to use -cas shown in the last point).

  • 如果按bash正常方式执行,main则调用。请注意,这不包括类似bash -x script(其中script不包含路径)的调用,请参见下文。

  • 如果来自bash,main则仅在调用脚本碰巧具有相同名称的情况下调用。(例如,如果它来自自身或通过bash -c 'someotherscript "$@"' main-script args..where main-scriptmust be,test则视为$BASH_SOURCE)。

  • 如果来源/执行/读取/eval由除 之外的外壳程序调用bashmain则不会调用(BASH_SOURCE始终与 不同$0)。

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

  • 如果由其他脚本中的bashwith evaleval "`cat script`"所有引号都很重要!)进行评估,则调用main. 如果eval直接从命令行运行,这类似于从标准输入读取脚本的前一种情况。(BASH_SOURCE是空白,而$0通常情况下/bin/bash如果不是被迫做一些完全不同的事情。)

  • 如果main没有被调用,它会返回true( $?=0)。

  • 这不依赖于意外行为(以前我写的是未记录的,但我没有发现您不能unset也不能更改的文档BASH_SOURCE):

    • BASH_SOURCE是一个 bash 保留数组。但是允许BASH_SOURCE=".$0"更改它会打开一个非常危险的蠕虫罐头,所以我的期望是,这一定不会产生任何影响(除非,可能在 的某些未来版本中出现一些丑陋的警告bash)。
    • 没有BASH_SOURCE在函数之外工作的文档。然而,相反的(它只适用于函数)都没有记录。观察结果是,它可以工作(用bashv4.3 和 v4.4测试,不幸的是我bash不再有v3.x)并且如果$BASH_SOURCE按照观察停止工作,太多脚本会中断。因此,我的期望是,BASH_SOURCE对于未来版本的bash.
    • 相比之下(不错的发现,顺便说一句!)考虑( return 0 ),它给出了0是否有来源和1如果没有来源。 这不仅对我来说有点出乎意料,而且(根据那里的读数)POSIX 说,return来自子外壳的行为是未定义的行为(return这里显然来自子外壳)。也许这个特性最终得到了足够广泛的使用,以至于它不能再被改变,但是 AFAICSbash在这种情况下,某些未来版本意外改变返回行为的可能性要高得多。
  • 不幸的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(bash不会将其传递$0给脚本,为此您需要-c按照最后一点所示使用)。

Thus, except for some some corner cases, mainis only called, when the script is executed the usual way. Normally this is, what you want,especially because it lacks complex hard to understand code.

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

Note that it is very similar to the Python code:

##代码##

Which also prevents calling of main, except for some corner cases, as you can import/load the script and enforce that __name__='__main__'

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

##代码##

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

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

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

If you have something, which can be sourced by multiple shells, it must be compatible. However (read the other answers), as there is no (easy to implement) portable way to detect the sourceing, you should change the rules.

如果你有一些东西,可以由多个 shell 获取,它必须是兼容的。但是(阅读其他答案),由于没有(易于实施)可移植的方式来检测sourceing,您应该更改规则

By enforcing that the script must be executed by /bin/bash, you exactly do this.

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

This solves all cases but followingin which case the script cannot run directly:

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

  • /bin/bashis not installed or disfunctional (i. E. in a boot environment)
  • If you pipe it to a shell like in curl https://example.com/script | $SHELL
  • (Note: This is only true if your bashis recent enough. This recipe is reported to fail for certain variants. So be sure to check it works for your case.)
  • /bin/bash未安装或无法正常工作(即在引导环境中)
  • 如果你把它用管道输送到一个壳里 curl https://example.com/script | $SHELL
  • (注意:这仅在您bash足够新的情况下才成立。据报告此配方对于某些变体失败。因此请务必检查它是否适合您的情况。)

However I cannot think about any real reason where you need that and also the ability to source the exactly same script in parallel! Usually you can wrap it to execute the mainby hand. Like that:

但是,我想不出您需要它的任何真正原因以及并行获取完全相同脚本的能力!通常您可以将其包装起来以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
  • $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

笔记

  • This answer would not have been possible without the help of all the other answers! Even the wrong ones - which initially made me posting this.

  • Update: Edited due to the new discoveries found in https://stackoverflow.com/a/28776166/490291

回答by jim mcnamara

This works later on in the script and does'nt depend on the _ variable:

这稍后在脚本中起作用并且不依赖于 _ 变量:

##代码##

or

或者

##代码##

回答by Wirawan Purwanto

I will give a BASH-specific answer. Korn shell, sorry. Suppose your script name is include2.sh; then make a function insidethe include2.shcalled am_I_sourced. Here's my demo version of include2.sh:

我将给出一个 BASH 特定的答案。科恩贝壳,对不起。假设您的脚本名称是include2.sh; 然后include2.sh调用的am_I_sourced. 这是我的演示版本include2.sh

##代码##

Now try to execute it in many ways:

现在尝试以多种方式执行它:

##代码##

So this works without exception, and it is not using the brittle $_stuff. This trick uses BASH's introspection facility, i.e. built-in variables FUNCNAMEand BASH_SOURCE; see their documentation in bash manual page.

所以这无一例外地有效,并且它没有使用易碎的$_东西。这个技巧使用了 BASH 的自省功能,即内置变量FUNCNAMEBASH_SOURCE;在 bash 手册页中查看他们的文档。

Only two caveat:

只有两个警告:

1) the call to am_I_calledmusttake place inthe sourced script, but not withinany function, lest ${FUNCNAME[1]}returns something else. Yeah...you could have checked ${FUNCNAME[2]}-- but you just make your life harder.

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

2) function am_I_calledmustreside in the sourced script if you want to find out what the name of the file being included.

2)如果您想找出所包含文件的名称,函数am_I_called必须驻留在源脚本中。

回答by user354193

I would like to suggest a small correction to Dennis' very helpful answer, to make it slightly more portable, I hope:

我想对丹尼斯非常有帮助的答案提出一个小小的更正,使其更便携,我希望:

##代码##

because [[isn't recognized by the (somewhat anal retentive IMHO) Debian POSIX compatibleshell, dash. Also, one may need the quotes to protect against filenames containing spaces, again in said shell.

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