我有一个脚本,如果它被源引用,我不想让它调用exit
。
我考虑检查$0 == bash
,但是如果脚本从另一个脚本中被源引用,或者用户从不同的shell(如ksh
)中源引用它,则存在问题。
有没有一种可靠的方法来检测脚本是否正在被源引用?
为 bash
, ksh
, zsh
提供健壮的解决方案,包括一个跨shell的解决方案,以及一个相对健壮的符合POSIX标准的解决方案:
给出的版本号是已验证功能的版本号 - 可能这些解决方案也适用于更早的版本,欢迎反馈。
仅使用 POSIX 功能(例如在Ubuntu上作为/bin/sh
的dash
),没有一种健壮的方法来确定脚本是否正在被调用 - 请参见下文了解最佳近似方案。
重要提示:
这些解决方案确定脚本是否被其调用者调用,其中调用者可能是一个shell本身,也可以是另一个脚本(它本身可能或可能不会被调用):
同时检测后一种情况会增加复杂性;如果您不需要检测当您的脚本被另一个脚本调用时的情况,您可以使用以下相对简单的符合POSIX标准的解决方案:
# Helper function
is_sourced() {
if [ -n "$ZSH_VERSION" ]; then
case $ZSH_EVAL_CONTEXT in *:file:*) return 0;; esac
else # Add additional POSIX-compatible shell names here, if needed.
case ${0##*/} in dash|-dash|bash|-bash|ksh|-ksh|sh|-sh) return 0;; esac
fi
return 1 # NOT sourced.
}
# Sample call.
is_sourced && sourced=1 || sourced=0
所有的解决方案都必须在脚本的顶层范围内运行,而不是在函数内部。
一行代码的解决方案如下 - 解释在下面;跨shell版本较为复杂,但应该能够稳定地工作:
(return 0 2>/dev/null) && sourced=1 || sourced=0
[[ "$(cd -- "$(dirname -- "$0")" && pwd -P)/$(basename -- "$0")" != "$(cd -- "$(dirname -- "${.sh.file}")" && pwd -P)/$(basename -- "${.sh.file}")" ]] && sourced=1 || sourced=0
[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0
(
[[ -n $ZSH_VERSION && $ZSH_EVAL_CONTEXT =~ :file$ ]] ||
[[ -n $KSH_VERSION && "$(cd -- "$(dirname -- "$0")" && pwd -P)/$(basename -- "$0")" != "$(cd -- "$(dirname -- "${.sh.file}")" && pwd -P)/$(basename -- "${.sh.file}")" ]] ||
[[ -n $BASH_VERSION ]] && (return 0 2>/dev/null)
) && sourced=1 || sourced=0
sourced=0
if [ -n "$ZSH_VERSION" ]; 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|-sh|dash|-dash) sourced=1;; esac
fi
(return 0 2>/dev/null) && sourced=1 || sourced=0
注意:该技术改编自user5754163的答案,因为它比原始解决方案[[ $0 != "$BASH_SOURCE" ]] && sourced=1 || sourced=0
[1]更加健壮
Bash只允许从函数中使用return
语句,并且在脚本的顶级范围内仅在脚本被"source"时才允许使用。
return
,则会发出错误消息,并将退出代码设置为1
。(return 0 2>/dev/null)
在一个子shell中执行return
并抑制错误消息;之后,退出代码指示脚本是否被源化(0
)或未被源化(1
),这与&&
和||
运算符一起使用以相应地设置sourced
变量。
return
将退出脚本。0
作为return
操作数使命令更加健壮;他指出:bash帮助中的return [N]
:“如果省略了N,则返回状态为最后一个命令的状态。”因此,早期版本(只使用return
而没有操作数)如果用户shell上的最后一个命令具有非零返回值,则会产生错误的结果。[[ "$(cd -- "$(dirname -- "$0")" && pwd -P)/$(basename -- "$0")" != "$(cd -- "$(dirname -- "${.sh.file}")" && pwd -P)/$(basename -- "${.sh.file}")" ]] && sourced=1 || sourced=0
特殊变量${.sh.file}
类似于$BASH_SOURCE
;请注意,${.sh.file}
会在bash、zsh和dash中导致语法错误,因此,请确保在多Shell脚本中以有条件的方式执行它。
与bash不同,在不同的时间点上,$0
和${.sh.file}
不保证相同,其中一个可能是相对路径或仅文件名,而另一个可能是完整路径。因此,在比较之前,必须将$0
和${.sh.file}
解析为完整路径。如果完整路径不同,则意味着要进行源文件操作。
[[ $ZSH_EVAL_CONTEXT =~ :file$) ]] && sourced=1 || sourced=0
$ZSH_EVAL_CONTEXT
包含有关评估上下文的信息: 用:
分隔的子字符串file
仅在脚本被源码引用时存在。
在被源码引用的脚本顶级范围内,$ZSH_EVAL_CONTEXT
以:file
结尾,这就是此测试限制的内容。在函数内部,会将:shfunc
添加到:file
; 在命令替换中,会将:cmdsubst
添加到:file
。
如果您愿意做出某些假设,您可以基于了解可能执行脚本的Shell二进制文件名,合理但并不绝对可靠地猜测您的脚本是否正在被源代码引用。
值得注意的是,这意味着这种方法无法检测到您的脚本是否被其他脚本源码引用。
在此答案的“如何处理源调用”一节中详细讨论了不能仅使用POSIX功能处理的边缘情况。
检查二进制文件名依赖于$0
的标准行为,例如zsh不会表现出这种行为。
因此,最安全的方法是将上面的稳健、特定于Shell的方法与$0
基于后备方案的结合使用,以处理所有剩余的Shell。
简而言之:以下解决方案:
在涵盖有shell特定测试的shell中:运行稳健。
在所有其他Shell中:仅在脚本直接从这样的Shell源代码引用时按预期工作, 而非从另一个脚本中。
向Stéphane Desneux和他的答案致敬,感谢他们鼓励我将跨Shell语句表达式转换成sh
兼容的if
语句并添加其他Shell处理方式。
sourced=0
if [ -n "$ZSH_VERSION" ]; 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|-sh|dash|-dash) sourced=1;; esac
fi
注意,为了保证鲁棒性,每个 shell 二进制文件名(例如 sh
)都表示两次 - 一次是原样的,另一次是前缀为 -
的形式。这是为了考虑到像 macOS 这样的环境,其中交互式 shell 以自定义的 $0
值作为 登录 shell 启动,并且该值是加上 -
前缀的(无路径的)shell 文件名。感谢 t7e。
(虽然 sh
和 dash
可能不太可能用作 交互式 shell,但您可能需要将其他需要添加到列表中的内容。)[1] user1902689 发现当您通过向 bash
二进制文件传递其 纯文件名 来执行位于 $PATH
中的脚本时,[[ $ 0!=“$ BASH_SOURCE”]]
会产生误报;例如: bash my-script
,因为此时 $0
只是 my-script
,而 $BASH_SOURCE
是完整路径。虽然通常您不会使用此技术来调用在 $PATH
中的脚本 - 您将直接调用它们(my-script
)- 但将其与 -x
结合使用有助于进行调试。
echo
自己的退出代码,即0
- 不管命令替换($(...)
)内部命令设置了什么退出代码;尝试echo $(nosuchcommand); echo $?
。 - mklement0case
分支条目('sh|-sh|...
)翻倍,而不是依赖使用sed
的命令替换。 - mklement0is_sourced()
函数的设计不是为了_退出_,而是为了_返回给调用者_并通过其退出代码(0
表示已来源,1
表示未来源)传达来源状态;示例语句is_sourced && sourced=1 || sourced=0
基于该代码执行。 - mklement0# 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 ..."
${BASH_SOURCE[0]}
时,是否有特别的原因,而不是只用$BASH_SOURCE
? ${0}
和 $0
之间有什么区别吗? - hrabanBASH_SOURCE
是一个数组变量(参见手册),它保存了一个源代码跟踪堆栈,其中${BASH_SOURCE[0]}
是最新的。这里使用大括号告诉bash哪些是变量名的一部分,对于此情况下的$0
来说它们不是必要的,但也不会造成任何影响。 ;) - Konrad$array
,默认情况下会得到 ${array[0]}
。所以,再次问一遍,是否有什么理由[...]? - Charles Duffy这似乎可以在Bash和Korn之间通用:
[[ $_ != $0 ]] && echo "Script is being sourced" || echo "Script is a subshell"
脚本的第一行或者shebang后面紧跟着的一行(如果使用shebang,应为ksh),必须包含类似于这样的一行或者像pathname="$_"
这样的赋值语句(后面需要测试和操作)才能正常工作。
BASH_ENV
,在脚本顶部的 $_
将会是从 BASH_ENV
中运行的最后一个命令。 - Mikel$ bash script.sh
那么,$_
将是 /bin/bash
而不是 ./script.sh
,这与你期望的情况不同,即以以下方式调用脚本:$ ./script.sh
在任何情况下,检测 $_
都会有问题。 - Wirawan Purwantobash
脚本(通过 shell 可执行文件调用,该解决方案将其错误地报告为 sourced),以及 (b)(发生的可能性较小)echo bash; . script
(如果 $_
恰好与源代码脚本的 shell 匹配,则此解决方案将其错误地报告为子 shell)。
只有 特定于 shell 的特殊变量(例如 $BASH_SOURCE
)可以提供稳健的解决方案(因此没有符合 POSIX 标准的稳健解决方案)。
虽然有点麻烦,但是可以制作一个稳健的跨 shell 测试。 - mklement0阅读了 @DennisWilliamson 的答案之后,发现存在以下问题:
由于这个问题涉及到 ksh 和 bash,因此该答案中还有关于 ksh 的另一部分... 请参见下文。
[ "$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
比较为了确保许多情况,我开始编写一个真实的脚本:
#!/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)
这个不再起作用。
将注释从第五行移到第六行会使答案更易读:
./testscript
_="./testscript", 0="./testscript" and BASH_SOURCE="./testscript"
proc: 26256[ppid:24890] is own
. testscript
_="_filedir", 0="bash" and BASH_SOURCE="testscript"
proc: 24890[ppid:24885] is sourced
source testscript
_="_filedir", 0="bash" and BASH_SOURCE="testscript"
proc: 24890[ppid:24885] is sourced
bash testscript
_="/bin/bash", 0="testscript" and BASH_SOURCE="testscript"
proc: 26317[ppid:24890] is own
env FILE=/dev/null ./testscript
_="/usr/bin/env", 0="./testscript" and BASH_SOURCE="./testscript"
proc: 26336[ppid:24890] is own
因为我不经常使用ksh,所以在阅读了man页面后,这是我的尝试:
#!/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
在sourced运行中,一些变量被继承了,但是并没有什么实际关联...
甚至可以检查$SECONDS
是否接近于0.000
,但这只适用于手动sourced的情况...
你甚至可以尝试检查父级是什么:
将此内容放入你的testfile.ksh
文件中:
ps $PPID
比:
./testfile.ksh
PID TTY STAT TIME COMMAND
32320 pts/4 Ss 0:00 -ksh
. ./testfile.ksh
PID TTY STAT TIME COMMAND
32319 ? S 0:00 sshd: user@pts/4
或者ps ho cmd $PPID
,但这只适用于一级子会话...
抱歉,在 ksh 下我找不到可靠的方法来实现这一点。
cat script | bash
)读入的脚本,请使用以下代码: [ "$0" = "$BASH_SOURCE" ] || [ -z "$BASH_SOURCE" ]
。 - hakre.
不是source
的别名,实际上是相反的。source somescript.sh
是 Bash 的特有语法,不具备可移植性;而. somescript.sh
则符合 POSIX 标准,具备可移植性(如果我没记错的话)。 - dragon788BASH_SOURCE[]
的答案(从bash-3.0开始)似乎最简单,尽管BASH_SOURCE[]
没有文档说明可以在函数体外工作(它目前可以在不同意man页面的情况下工作)。
最健壮的方法是像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
的输出,值main
和source
区分了调用者的上下文。使用FUNCNAME[]
可以避免捕获和解析caller
输出。但是你需要知道或计算正确的本地调用深度。在其他函数或脚本中源自另一个脚本的情况会导致数组(堆栈)更深。(FUNCNAME
是一个特殊的bash数组变量,它应该有与调用堆栈对应的连续索引,只要它从未被unset
。)
function issourced() {
[[ ${FUNCNAME[@]: -1} == "source" ]]
}
${FUNCNAME[-1]}
代替数组中的最后一项。感谢Dennis Williamson在下面的评论中提供的改进和简化。)return 2>/dev/null || exit
return
将终止被引用的脚本并返回到调用者。如果脚本正在执行,则return
将返回错误(重定向),而exit
将像平常一样终止脚本。如果需要,return
和exit
均可接受退出代码。遗憾的是,这在ksh
中不起作用(至少在我这里的AT&T衍生版本中不起作用),如果在函数或点源脚本之外调用,则将return
视为等同于exit
。
更新:在现代版的ksh
中,您可以检查特殊变量.sh.level
,它设置为函数调用深度。对于被调用的脚本,初始设置为空白,而对于点源脚本,它将被设置为1。function issourced {
[[ ${.sh.level} -eq 2 ]]
}
issourced && echo this script is sourced
issourced()
来测试文件中的内容。ksh
纪律函数和一些调试陷阱技巧来模拟bash FUNCNAME
数组。)$-
作为另一个指示器(虽然不完美)来表示shell状态。FUNCNAME[]
中,但只要测试该数组中的最后一项,就没有歧义。
- 我没有pdksh的好答案。我找到的最接近的东西仅适用于pdksh
,其中每个脚本源打开一个新的文件描述符(从原始脚本开始,从10开始)。几乎肯定不是您想依赖的东西...${FUNCNAME[(( ${#FUNCNAME[@]} - 1 ))]}
来获取堆栈中的最后(底部)项?然后针对 "main" 进行测试(取反为 OP)对我来说是最可靠的。 - Adrian GünterPROMPT_COMMAND
,那么当我运行source sourcetest.sh
时,它会显示为FUNCNAME
数组的最后一个索引。反转检查(查找main
作为最后一个索引)似乎更加健壮:is_main() { [[ ${FUNCNAME[@]: -1} == "main" ]]; }
。 - dimo414FUNCNAME
仅在函数中可用。但是,通过 declare -p FUNCNAME
进行测试后,bash
的行为不同。v4.3 在函数外部会报错,而 v4.4 则会返回 declare -a FUNCNAME
。无论何种情况下,${FUNCNAME[0]}
在主脚本中(如果被执行)都会返回 main
,而 $FUNCNAME
则不会返回任何内容。此外,有很多脚本在函数外“滥用” $BASH_SOURCE
,因此我怀疑这种情况可能不会或不会被更改。 - Tino${FUNCNAME[-1]}
技术一年后,我已经完全撤回了。它在嵌套脚本中无法正常工作!您必须根据嵌套的级别手动更新索引,这既不可预见也不实用。然而,使用if [ "${BASH_SOURCE[0]}" = "$0" ]
的技术在运行或源另一个脚本的嵌套脚本中非常有效。因此,我已经大量更新了我的答案,并将我最喜欢的技术放在了这里的顶部。 - Gabriel Staples编辑注:该答案的解决方案非常稳健,但仅限于bash。它可以简化为(return 2>/dev/null)
。
简短概述
尝试执行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
shell-prompt> return >/dev/null 2>&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
$ readlink $(which sh)
dash
$ . test.sh
此脚本被引用。
$ ./test.sh
此脚本被引用。
- Phil Rutschmanreturn
应该做什么(http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_24_03)。`dash` shell将在顶层使用的return
视为exit
。其他像bash
或zsh
这样的shell不允许在顶层使用return
,而这正是此技术所利用的特性。 - user5754163$
,则它可以在sh中工作。也就是说,使用(return >/dev/null 2>&1)
代替$(return >/dev/null 2>&1)
- 但这样在bash中停止工作。 - Eponymousdash
在Ubuntu上的作用类似于sh
,因此该解决方案在sh
中通常不起作用。该解决方案对我来说在Bash 3.2.57和4.4.5中有效 - 在(...)
之前是否有$
都可以(尽管没有好的理由使用$
)。 - mklement0return
在源脚本中编写,但它被调用的上下文是顶层的,因此它应该总是失败。显然,它不会失败,这是一个错误,因此有一天可能会被修复,到那时,这将停止工作。 - Jan Hudec提供参考,阅读其他答案后,我为自己想出了以下解决方案:
更新:实际上,有人发现另一个答案中的错误已经被更正,这也影响到了我的答案。如果您感兴趣,可以查看编辑内容。
这适用于所有以#!/bin/bash
起始的脚本,但是可能会被不同的shell引用,用于学习一些保存在main
函数之外的信息(例如设置)。
根据下面的评论,这个答案显然并不适用于所有的
bash
变体。 也不适用于/bin/sh
基于bash
的系统。即,在MacOS上,bash v3.x会失败。(目前我不知道如何解决这个问题。)
#!/bin/bash
# Function definitions (API) and shell variables (constants) go here
# (This is what might be interesting for other shells, too.)
# this main() function is only meant to be meaningful for bash
main()
{
# The script's execution part goes here
}
BASH_SOURCE=".$0" # cannot be changed in bash
test ".$0" != ".$BASH_SOURCE" || main "$@"
您可以使用以下代码代替最后2行,以避免在其他shell中设置BASH_SOURCE
并允许set -e
在main
中工作(我认为这种方法不太易读):
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
)。)
如果被除bash
之外的其他shell引用/执行/读取/eval
,则不会调用main
(BASH_SOURCE
始终与$0
不同)。
如果bash
从标准输入读取脚本,则不会调用main
,除非您将$0
设置为空字符串,如:( exec -a''/bin/bash) <script
如果使用eval
(eval"`cat script`"
所有引号都很重要!)从其他脚本中评估,则调用main
。如果直接从命令行运行eval
,则类似于上一个案例,在该案例中,脚本从标准输入读取。(BASH_SOURCE
为空,而$0
通常为/bin/bash
,如果没有被强制更改为完全不同的东西。)
如果未调用main
,则会返回true
($?=0
)。
这不依赖于意外行为(以前我写过未记录的内容,但我没有发现您无法取消设置或更改BASH_SOURCE
的任何文档),因为:
BASH_SOURCE
是保留数组。但是,允许BASH_SOURCE=".$0"
更改它将打开一个非常危险的问题,因此我的期望是,这必须没有影响(除非在某个未来版本的bash
中出现一些丑陋的警告)。 BASH_SOURCE
在函数之外起作用。但是相反的情况(它仅在函数中起作用)也没有记录。观察结果是它可以工作(已测试使用bash
v4.3和v4.4,不幸的是我不再有bash
v3.x),而且相当多的脚本将中断,如果$BASH_SOURCE
停止按预期工作。因此,我的期望是,BASH_SOURCE
在未来版本的bash
中保持不变。(return 0)
,如果被引用,则返回0
,如果没有被引用,则返回1
。这对我来说有点出乎意料, 根据那里的读数,POSIX表示从子shell返回的行为未定义(而此处的return
显然来自子shell)。也许这个功能最终main
函数。通常这就是您想要的,特别是因为它缺乏复杂难以理解的代码。
Note that it is very similar to the Python code:
if __name__ == '__main__': main()
Which also prevents calling of
main
, except for some corner cases, as you can import/load the script and enforce that__name__='__main__'
如果你有一些能被多个shell调用的东西,它必须是兼容的。然而(请参阅其他答案),由于没有(易于实现的)便携式方法来检测源,因此您应该更改规则。
通过强制脚本必须由/bin/bash
执行,您就可以做到这一点。
这解决了所有情况,但以下情况除外:
/bin/bash
未安装或不起作用(例如在启动环境中)curl https://example.com/script | $SHELL
这样的shell中bash
足够新时才是真实的。据报道,此方法对某些变体无效。因此,请务必检查它是否适用于您的情况。)但是,我想不出任何真正需要同时引用完全相同的脚本的原因!通常,您可以将其包装以手动执行main
。就像这样:
$SHELL -c '. 脚本 && 主函数'
{ curl https://example.com/脚本 && echo && echo 主函数; } | $SHELL
$SHELL -c 'eval "`curl https://example.com/脚本`" && 主函数'
echo 'eval "`curl https://example.com/脚本`" && 主函数' | $SHELL
感谢所有其他答案的帮助!即使是错误的答案-这让我发布了这个。
更新:由于在https://dev59.com/GXE85IYBdhLWcg3wpFEa#28776166中发现的新发现而进行编辑。
<your-script>
”(源)是可行的,但只有当脚本明确编写为仅使用POSIX功能时才有意义,以防止特定于一个shell的功能破坏其他shell中的执行;因此,使用_Bash_ shebang行(而不是#!/bin/sh
)会令人困惑-至少没有显眼的注释。
相反,如果您的脚本仅适用于Bash(即使仅仅是因为没有考虑哪些功能可能不可移植),最好在非Bash shell中_refuse_执行。 - mklement0$BASH_SOURCE
,因此在v3.2.57中子shell始终失败,因此当您从Bash / Bash作为sh
调用时,无论您是否源脚本,都将始终执行main
。 - mklement0main
,但在这种情况下它确实执行了!当通过/bin/sh
进行调用时,即使是bash --posix
,在这种情况下也会发生同样的情况,这显然是错误的。 - Tino这个在脚本后面运行,不依赖于变量 _:
## 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
is_sourced() { case $(basename "${0#-}") in sh|bash|ash|dash|ksh) return 0;; *) return 1;; esac }
- zeroxinclude2.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的自省设施,即内置变量FUNCNAME
和BASH_SOURCE
;请参阅它们在bash手册页面中的文档。am_I_called
的调用必须发生在被引用的脚本中,但不能在任何函数内部,否则${FUNCNAME[1]}
会返回其他内容。是的...你可以检查${FUNCNAME[2]}
——但那只会让你的生活更难。am_I_called
函数必须位于被引用的脚本中。[ "$_" != "$0" ] && echo "Script is being sourced" || echo "Script is a subshell"
因为 (在我看来有点过于苛刻的) Debian 兼容 POSIX shell dash
不认识 [[
。此外,在该 shell 中,文件名包含空格时可能需要使用引号进行保护。