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
How to detect if a script is being sourced
提问by brianegge
I have a script where I do not want it to call exit
if it's being sourced.
我有一个脚本,exit
如果它是来源,我不希望它调用。
I thought of checking if $0 == bash
but 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/sh
on 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
return
statements only from functions and, in a script's top-level scope, only if the script is sourced.- If
return
is used in the top-level scope of a non-sourcedscript, an error message is emitted, and the exit code is set to1
.
- If
(return 0 2>/dev/null)
executesreturn
in 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 thesourced
variable accordingly.- Use of a subshell is necessary, because executing
return
in 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
0
as thereturn
operand; he notes: per bash help ofreturn [N]
: "If N is omitted, the return status is that of the last command." As a result, the earlier version [which used justreturn
, without an operand] produces incorrect result if the last command on the user's shell has a non-zero return value.
- Use of a subshell is necessary, because executing
Bash
return
仅允许来自函数的语句,并且在脚本的顶级范围内,仅当脚本来自.- 如果
return
用于非源脚本的顶级范围,则会发出错误消息,并且退出代码设置为1
。
- 如果
(return 0 2>/dev/null)
return
在子shell中执行并抑制错误消息;之后,退出代码指示脚本是否来自 (0
) 或不是 (1
),它与&&
和||
运算符一起使用以相应地设置sourced
变量。- 使用子 shell 是必要的,因为
return
在源脚本的顶级范围内执行会退出脚本。 - 向@Haozhun 致敬,他通过显式
0
用作return
操作数使命令更加健壮;他指出:根据 bash 帮助return [N]
:“如果省略 N,则返回状态是最后一个命令的状态。” 因此,return
如果用户 shell 上的最后一个命令具有非零返回值,则早期版本 [仅使用,不使用操作数] 会产生不正确的结果。
- 使用子 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, $0
and ${.sh.file}
are NOT guaranteed to be exactlyidentical in the non-sourced case, as $0
may be a relativepath, while ${.sh.file}
is always a fullpath, so $0
must 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_CONTEXT
contains information about the evaluation context - call this outside of a function. Inside a sourced script['s top-level scope], $ZSH_EVAL_CONTEXT
endswith :file
.
$ZSH_EVAL_CONTEXT
包含有关评估上下文的信息 - 在函数外部调用它。内的执行的脚本['的顶层范围],$ZSH_EVAL_CONTEXT
端部带有:file
。
Caveat: Inside a command substitution, zsh appends :cmdsubst
, so test $ZSH_EVAL_CONTEXT
for :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 if
statement 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 $PATH
by passing its mere filenameto the bash
binary; e.g., bash my-script
, because $0
is then just my-script
, whereas $BASH_SOURCE
is 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 -x
for debugging.
[1] user1902689发现[[ $0 != "$BASH_SOURCE" ]]
当您通过将其单纯的文件名传递给二进制文件来执行位于 中的$PATH
脚本时会产生误报;例如,因为是 then just ,而是完整路径。虽然你通常不会使用这一技术在调用的脚本-你会只是调用它们直接() -它是在与结合有用的调试。bash
bash my-script
$0
my-script
$BASH_SOURCE
$PATH
my-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.
由于这个问题代表ksh和bash,因此这个答案中还有另一部分关于ksh......见下文。
Simple bashway
简单的bash方式
##代码##Let's try (on the fly because that bash could ;-):
让我们尝试一下(因为那个 bash 可以 ;-):
##代码##I use source
instead off .
for readability (as .
is an alias to source
):
我使用source
off.
来提高可读性(作为.
的别名source
):
Note that process number don't change while process stay sourced:
请注意,流程编号不会更改,而流程保持来源:
##代码##Why not to use $_ == $0
comparison
为什么不使用$_ == $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 -x
flag:
但是,在添加-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 $SECONDS
is 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 main
and source
distinguish the caller's context. Using FUNCNAME[]
saves you capturing and parsing caller
output. 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. (FUNCNAME
is a special bash array variable, it should have contiguous indexes corresponding to the call stack, as long as it is never unset
.)
这相当于检查 的输出caller
、值main
并source
区分调用者的上下文。使用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 bash
idiom for this situation is:
但是,如上所述,您的问题是“我有一个脚本,如果它被采购,我不希望它调用'退出'”。bash
这种情况的常见习语是:
If the script is being sourced then return
will terminate the sourced script and return to the caller.
如果正在获取return
脚本,则将终止获取的脚本并返回给调用者。
If the script is being executed, then return
will return an error (redirected), and exit
will terminate the script as normal. Both return
and exit
can take an exit code, if required.
如果脚本正在执行,return
则将返回错误(重定向),并将exit
正常终止脚本。双方return
并exit
可以采取的退出代码,如果需要的话。
Sadly, this doesn't work in ksh
(at least not in the AT&T derived version I have here), it treats return
as equivalent to exit
if invoked outside a function or dot-sourced script.
可悲的是,这在ksh
(至少不是我这里的 AT&T 派生版本中)不起作用,它被视为return
等效于exit
在函数或点源脚本之外调用。
Updated: What you cando in contemporary versions of ksh
is to check the special variable .sh.level
which 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 ksh
discipline function and some debug trap trickery to emulate the bash FUNCNAME
array.)
(您可能也对 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 topdksh
, 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 return
statement. 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 return
statement 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 return
statement inside of a sub-shell. When the return
statement 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/bash
but might be sourced by different shells as well to learn some information (like settings) which is are kept outside the main
function.
这适用于所有脚本,这些脚本开始#!/bin/bash
但也可能来自不同的 shell,以了解一些保存在main
函数之外的信息(如设置)。
According to the comments below, this answer here apparently does not work for all
bash
variants. Also not for systems, where/bin/sh
is based onbash
. I. E. it fails forbash
v3.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_SOURCE
in other shells and allow set -e
to work in main
:
您可以使用以下(在我看来不太可读)代码而不是最后 2 行代码来不在BASH_SOURCE
其他 shell 中设置并允许set -e
在其中工作main
:
This script-recipe has following properties:
此脚本配方具有以下属性:
If executed by
bash
the normal way,main
is called. Please note that this does not include a call likebash -x script
(wherescript
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 viabash -c 'someotherscript "$@"' main-script args..
wheremain-script
must be, whattest
sees as$BASH_SOURCE
).If sourced/executed/read/
eval
ed by a shell other thanbash
,main
is not called (BASH_SOURCE
is always different to$0
).main
is not called ifbash
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
witheval
(eval "`cat script`"
all quotes are important!) from within some other script, this callsmain
. Ifeval
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 returntrue
($?=0
).This does not rely on unexpected behavior (previously I wrote undocumented, but I found no documentation that you cannot
unset
nor alterBASH_SOURCE
either):BASH_SOURCE
is a bash reserved array. But allowingBASH_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 ofbash
).- 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 withbash
v4.3 and v4.4, unfortunately I have nobash
v3.x anymore) and that quite too many scripts would break, if$BASH_SOURCE
stops working as observed. Hence my expectation is, thatBASH_SOURCE
stays as is for future versions ofbash
, too. - In contrast (nice find, BTW!) consider
( return 0 )
, which gives0
if sourced and1
if not sourced. This comes a bit unexpected not only for me , and (according to the readings there) POSIX says, thatreturn
from subshell is undefined behavior (and thereturn
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 futurebash
version accidental changes the return behavior in that case.
Unfortunately
bash -x script 1 2 3
does not runmain
.(Comparescript 1 2 3
wherescript
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 runmain
can be considered a feature.
Note that
( exec -a none script )
callsmain
(bash
does not pass it's$0
to the script, for this you need to use-c
as shown in the last point).
如果按
bash
正常方式执行,main
则调用。请注意,这不包括类似bash -x script
(其中script
不包含路径)的调用,请参见下文。如果来自
bash
,main
则仅在调用脚本碰巧具有相同名称的情况下调用。(例如,如果它来自自身或通过bash -c 'someotherscript "$@"' main-script args..
wheremain-script
must be,test
则视为$BASH_SOURCE
)。如果来源/执行/读取/
eval
由除 之外的外壳程序调用bash
,main
则不会调用(BASH_SOURCE
始终与 不同$0
)。main
如果bash
从 stdin 读取脚本,则不会调用,除非您设置$0
为空字符串,如下所示:( exec -a '' /bin/bash ) <script
如果由其他脚本中的
bash
witheval
(eval "`cat script`"
所有引号都很重要!)进行评估,则调用main
. 如果eval
直接从命令行运行,这类似于从标准输入读取脚本的前一种情况。(BASH_SOURCE
是空白,而$0
通常情况下/bin/bash
如果不是被迫做一些完全不同的事情。)如果
main
没有被调用,它会返回true
($?=0
)。这不依赖于意外行为(以前我写的是未记录的,但我没有发现您不能
unset
也不能更改的文档BASH_SOURCE
):BASH_SOURCE
是一个 bash 保留数组。但是允许BASH_SOURCE=".$0"
更改它会打开一个非常危险的蠕虫罐头,所以我的期望是,这一定不会产生任何影响(除非,可能在 的某些未来版本中出现一些丑陋的警告bash
)。- 没有
BASH_SOURCE
在函数之外工作的文档。然而,相反的(它只适用于函数)都没有记录。观察结果是,它可以工作(用bash
v4.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, main
is 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 source
ing, you should change the rules.
如果你有一些东西,可以由多个 shell 获取,它必须是兼容的。但是(阅读其他答案),由于没有(易于实施)可移植的方式来检测source
ing,您应该更改规则。
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/bash
is 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
bash
is 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 main
by 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
如果没有所有其他答案的帮助,这个答案是不可能的!即使是错误的 - 这最初让我发布这个。
更新:根据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.sh
called 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 FUNCNAME
and BASH_SOURCE
; see their documentation in bash manual page.
所以这无一例外地有效,并且它没有使用易碎的$_
东西。这个技巧使用了 BASH 的自省功能,即内置变量FUNCNAME
和BASH_SOURCE
;在 bash 手册页中查看他们的文档。
Only two caveat:
只有两个警告:
1) the call to am_I_called
musttake 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_called
mustreside 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 中,可能需要引号来防止包含空格的文件名。