我有一个脚本,exit如果它是源代码,我不希望它调用。
我想检查一下是否可以,$0 == bash但是如果脚本是从另一个脚本中获取的,或者用户是从其他shell那里获取的,这就会出现问题ksh。
有没有可靠的方法来检测脚本是否源?
我有一个脚本,exit如果它是源代码,我不希望它调用。
我想检查一下是否可以,$0 == bash但是如果脚本是从另一个脚本中获取的,或者用户是从其他shell那里获取的,这就会出现问题ksh。
有没有可靠的方法来检测脚本是否源?
Answers:
这似乎可以在Bash和Korn之间移植:
[[ $_ != $0 ]] && echo "Script is being sourced" || echo "Script is a subshell"
pathname="$_"必须在脚本的第一行或shebang之后的行上添加与此类似的行或类似的任务(以及稍后的测试和操作)(如果使用的话,应在ksh中使用,以便在ksh下运行)大多数情况)。
BASH_ENV,$_则脚本的顶部将是最后一个从中运行的命令BASH_ENV。
$_都是一个问题。
bash script(通过shell可执行文件调用,此解决方案将其错误地报告为源),以及(b)(可能性很小)echo bash; . script(如果$_恰好与提供脚本的shell匹配,则此解决方案将其错误报告为:一个subshell)。只有特定于外壳的特殊变量(例如$BASH_SOURCE)才允许使用健壮的解决方案(因此,没有健壮的POSIX兼容解决方案)。它是可能的,虽然麻烦,制作一个健壮的跨壳体试验。
如果您的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 ..."
${BASH_SOURCE[0]}而不是仅仅使用的理由$BASH_SOURCE吗?和${0}vs $0?
$array,则${array[0]}默认情况下。那么,又有原因[...]吗?
对于强大的解决方案bash,ksh,zsh,包括跨壳一个,再加上合理稳健的POSIX兼容的解决方案:
给出的版本号是经过验证的功能的版本号-可能的是,这些解决方案可以在更早的版本上运行,也欢迎反馈。
仅使用POSIX功能(例如in dash,与 /bin/sh在Ubuntu上相同),没有可靠的方法来确定是否源脚本-参见以下最佳近似。
单线遵循-以下说明;跨外壳版本很复杂,但是应该可以正常运行:
bash(在3.57和4.4.19上验证)
(return 0 2>/dev/null) && sourced=1 || sourced=0ksh(已在93u +上验证)
[[ $(cd "$(dirname -- "$0")" &&
printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] &&
sourced=1 || sourced=0zsh(在5.0.5上验证)-确保在函数外部调用此函数
[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0跨壳(bash,ksh,zsh)
([[ -n $ZSH_EVAL_CONTEXT && $ZSH_EVAL_CONTEXT =~ :file$ ]] ||
[[ -n $KSH_VERSION && $(cd "$(dirname -- "$0")" &&
printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] ||
[[ -n $BASH_VERSION ]] && (return 0 2>/dev/null)) && sourced=1 || sourced=0POSIX兼容 ; 不是一个班轮(单管)因技术原因而无法完全健壮(参见下图):
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说明:
(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)return在子shell中执行并抑制错误消息;之后,退出代码将指示脚本是源(0)还是源(),该脚本1与&&和||运算符一起使用以相应地设置sourced变量。
return在源脚本的顶级范围内执行将退出该脚本。0用作return操作数使命令更加健壮;他指出:根据bash的帮助return [N]:“如果省略N,则返回状态为最后一条命令的返回状态。” 结果,return如果用户外壳程序上的最后一条命令的返回值非零,则较早的版本[仅使用,而没有操作数]会产生错误的结果。[[ \
$(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_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0
$ZSH_EVAL_CONTEXT包含有关评估上下文的信息-在函数外部调用此函数。内的执行的脚本['的顶层范围],$ZSH_EVAL_CONTEXT 端部带有:file。
警告:在命令替换内,zsh appends :cmdsubst,因此$ZSH_EVAL_CONTEXT在 :file:cmdsubst$那里进行测试。
如果您愿意做出某些假设,则可以基于知道可能正在执行脚本的shell 的二进制文件名,对脚本是否源进行合理,但不是万无一失的猜测。
值得注意的是,这意味着如果您的脚本是由另一个脚本来源的,则此方法将失败。
我的答案中的“如何处理源调用”一节详细讨论了POSIX功能无法处理的极端情况。
这依赖于标准的行为$0,其中zsh,例如没有不表现。
因此,最安全的方法是将上述健壮的,特定于外壳的方法与所有其余外壳的后备解决方案结合起来。
给斯特凡·德斯纽(StéphaneDesneux)的提示和对灵感的回答(将我的跨shell语句表达式转换为sh-compatible if语句,并为其他shell添加处理程序)。
sourced=0
if [ -n "$ZSH_EVAL_CONTEXT" ]; then
case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
elif [ -n "$KSH_VERSION" ]; then
[ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ] && sourced=1
elif [ -n "$BASH_VERSION" ]; then
(return 0 2>/dev/null) && sourced=1
else # All other shells: examine $0 for known shell binary filenames
# Detects `sh` and `dash`; add additional shell filenames as needed.
case ${0##*/} in sh|dash) sourced=1;; esac
fi
[1] user1902689发现,通过将[[ $0 != "$BASH_SOURCE" ]]脚本的纯文件名传递给二进制文件来执行位于中的$PATH脚本时会产生误报。例如,因为是那么就,而是完整路径。虽然你通常不会使用这一技术在调用的脚本-你会只是调用它们直接() -它是在与结合有用的调试。bashbash my-script$0my-script$BASH_SOURCE$PATHmy-script-x
阅读@DennisWilliamson的答案后,存在一些问题,请参见下文:
正如这个问题代表 sh 和 重击,此答案中还有另一部分涉及 sh... 见下文。
[ "$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改用off .来提高可读性(作为.的别名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)
这将不再起作用。
将注释从第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
因为我不使用 sh 在手册页上进行一些阅读后,有很多尝试:
#!/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,但这仅适用于一个级别的会话。
抱歉,在以下情况下,我找不到可靠的方法 sh。
[ "$0" = "$BASH_SOURCE" ] || [ -z "$BASH_SOURCE" ]用于通过管道(cat script | bash)读取的脚本。
.不是的别名source,实际上是相反的。source somescript.sh是Bash-ism,不是便携式的,. somescript.sh是POSIX和便携式IIRC。
将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,值的输出main并source区分调用者的上下文。使用FUNCNAME[]保存您捕获和解析caller输出。不过,您需要知道或计算本地通话深度是否正确。诸如从其他函数或脚本中获取脚本的情况将导致数组(堆栈)更深。(FUNCNAME是一个特殊的bash数组变量,它应该具有与调用堆栈相对应的连续索引,只要它从不为准unset。)
function issourced() {
[[ ${FUNCNAME[@]: -1} == "source" ]]
}
(在bash-4.2和更高版本中,您可以使用更简单的形式${FUNCNAME[-1]}代替数组中的最后一项。由于下面的Dennis Williamson的评论,因此得到了改进和简化。)
但是,您所说的问题是“ 我有一个脚本,如果要获取它,我不希望它调用'exit' ”。bash这种情况的常见成语是:
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
它不如bash版本健壮,必须issourced()在顶级或已知功能深度的测试文件中调用。
(您可能也对github上的此代码感兴趣,该代码使用了ksh纪律函数和一些调试陷阱技巧来模拟bash FUNCNAME数组。)
此处的规范答案:http : //mywiki.wooledge.org/BashFAQ/109还提供$-了壳状态的另一个指示(尽管不完美)。
笔记:
FUNCNAME[]但是只要测试该数组中的最后一项就不会有歧义。pdksh。我能找到的最接近的内容仅适用于pdksh,其中每次脚本采购都会打开一个新的文件描述符(原始脚本以10开始)。几乎可以肯定,您不想依靠...${FUNCNAME[(( ${#FUNCNAME[@]} - 1 ))]}获得堆栈中的最后一个(底部)项目?然后对我来说,针对“ main”(否定OP)进行测试是最可靠的。
PROMPT_COMMAND集合,则在运行时显示为FUNCNAME数组的最后一个索引source sourcetest.sh。反转检查(寻找main最后一个索引)似乎更可靠:is_main() { [[ ${FUNCNAME[@]: -1} == "main" ]]; }。
编者注:该答案的解决方案功能强大,但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
您还希望return在子外壳程序内执行该语句。当return语句运行它。。。好 。。。返回。如果在子外壳程序中执行它,它将从该子外壳程序中返回,而不是从脚本中返回。要在子外壳中执行,请将其包装在中$(...):
shell-prompt> $(return >/dev/null 2>$1)
现在,您可以看到子shell的退出代码,应该为1,因为在子shell内引发了错误:
shell-prompt> echo $?
output: 1
$ readlink $(which sh) dash $ . test.sh This script is sourced. $ ./test.sh This script is sourced.
return在顶层应该做的事情(pubs.opengroup.org/onlinepubs/9699919799/utilities/…)。该dashshell对一个return在顶部水平exit。其他外壳在顶层不允许bash或zsh不允许return,这是这种漏洞利用技术的功能。
$子外壳程序之前的命令,它将在sh中工作。也就是说,使用(return >/dev/null 2>&1)而不是$(return >/dev/null 2>&1)-,但是它将停止在bash中工作。
dash在该解决方案不起作用sh的情况下,它在Ubuntu上起作用,因此该解决方案通常不适用于sh。该解决方案确实在Bash 3.2.57和4.4.5中对我有用-有或没有$以前(...)(尽管从来没有充分的理由$)。
return如果source没有正确的命令,在脚本执行时,一个明显的返回值就会中断。建议进行增强编辑。
FWIW,在阅读了所有其他答案之后,我为我提出了以下解决方案:
更新:实际上,有人在另一个答案中发现了一个自纠正错误,这也影响了我的。我认为这里的更新也是一种改进(如果您感到好奇,请参阅编辑)。
这适用于所有以开头的#!/bin/bash脚本,但可能来自不同的shell,也可以学习一些保留在main函数外部的信息(如设置)。
根据下面的评论,此答案显然不适用于所有
bash变体。还没有对系统,其中/bin/sh基于bash。IEbash在MacOS上无法运行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 "$@"
您可以使用以下代码(我认为可读性较差)代替最后两行,以免BASH_SOURCE在其他shell中设置并允许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)。
如果eval不是由以外的shell来执行/执行/读取/ 编辑的bash,main则不会调用(BASH_SOURCE始终与$0)。
main如果bash从stdin读取脚本,则不会调用它,除非您将其设置$0为空字符串,如下所示:( exec -a '' /bin/bash ) <script
如果在其他脚本中bash使用来评估eval (eval "`cat script`" 所有引号都很重要!),则调用main。如果eval直接从命令行运行,则这与以前的情况类似,后者是从stdin中读取脚本的。(BASH_SOURCE为空白,$0通常/bin/bash不强制使用完全不同的东西。)
如果main未调用,它将返回true($?=0)。
这不依赖于意外行为(以前我写的没有文档记录,但是我没有找到您不能unset也不能更改的文档BASH_SOURCE):
BASH_SOURCE是bash保留的数组。但是允许BASH_SOURCE=".$0"更改它会打开一个非常危险的蠕虫罐,所以我的期望是,它一定没有效果(除非,将来的某些版本中会显示一些难看的警告bash)。BASH_SOURCE可以在功能之外使用。但是,相反的内容(仅适用于函数)均未记录。可以观察到的是,它可以正常工作(通过bashv4.3和bashv4.4进行了测试,不幸的是我再没有v3.x了),并且如果$BASH_SOURCE停止运行,则太多脚本将被破坏。因此,我的期望是,BASH_SOURCE与的未来版本一样bash。( return 0 ),它给出了0是否来源和1是否来源。 POSIX说,这不仅对我来说有点意外,而且(根据那里的阅读资料)POSIX说,return来自子shell的行为是不确定的(return这里显然来自子shell)。也许此功能最终会得到足够的广泛使用,以致无法再进行更改,但是bash在这种情况下,AFAICS很有可能会出现某些将来版本意外更改返回行为的情况。不幸的bash -x script 1 2 3是没有运行main。(比较script 1 2 3其中script没有路径)。以下可以用作解决方法:
bash -x "`which script`" 1 2 3bash -xc '. script' "`which script`" 1 2 3bash script 1 2 3不运行main可以视为一项功能。请注意,( exec -a none script )调用main(bash不会将其传递$0给脚本,为此,您需要使用-c最后一点所示的方法)。
因此,除某些特殊情况外,main仅在以常规方式执行脚本时才调用。 通常,这就是您想要的,尤其是因为它缺少复杂的难以理解的代码。
请注意,它与Python代码非常相似:
if __name__ == '__main__': main()
main除某些特殊情况外,这还可以防止调用,因为您可以导入/加载脚本并强制执行__name__='__main__'
如果您有某些东西(可以由多个外壳提供),那么它必须兼容。但是(请阅读其他答案),由于没有(易于实现)可移植的方式来检测信号source,因此您应该更改规则。
通过强制必须由执行脚本/bin/bash,您可以完全做到这一点。
这解决了所有情况,但在以下情况下,脚本无法直接运行:
/bin/bash 未安装或无法正常运行(即在引导环境中)curl https://example.com/script | $SHELLbash的病历足够近时,这才是正确的。据报道,此食谱在某些型号上失败了。因此,请务必检查它是否适合您的情况。)但是,我无法考虑您真正需要它的任何真正原因,也无法考虑并行获取完全相同的脚本的能力!通常,您可以将其包装起来以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没有所有其他答案的帮助,这个答案将是不可能的!即使是错误的-最初也使我发布了此信息。
更新:由于在https://stackoverflow.com/a/28776166/490291中发现了新发现而进行了编辑
/bin/sh(实际上是bash在POSIX模式下),分配给BASH_SOURCE 会破坏脚本。在其他炮弹(dash,ksh,zsh),通过将它作为一个文件参数调用脚本直接到shell可执行故障(比如,zsh <your-script>将让你的脚本错误地认为这是来源)。(你已经提到,管道代码故障,在所有的炮弹。)
. <your-script>(采购)原则上可以在所有类似POSIX的外壳中工作,但是只有明确编写脚本以仅使用POSIX功能才有意义,以防止特定于一个外壳的功能破坏执行在其他炮弹中;因此,使用Bash shebang行(而不是#!/bin/sh)会造成混淆-至少没有明显的注释。相反,如果您的脚本仅打算从Bash运行(即使仅由于不考虑哪些功能可能不可移植而已),则最好在非Bash Shell中拒绝执行。
main,但是在这种情况下,它将执行此操作!而当通过采购/bin/sh,也就是bash --posix,同样的情况在这种情况下,这是完全错误的为好。
我将给出一个针对BASH的答案。可恩壳,对不起。假设您的脚本名称是include2.sh;然后在被include2.sh调用的函数中创建一个函数am_I_sourced。这是我的演示版本include2.sh:
am_I_sourced()
{
if [ "${FUNCNAME[1]}" = source ]; then
if [ "$1" = -v ]; then
echo "I am being sourced, this filename is ${BASH_SOURCE[0]} and my caller script/shell name was $0"
fi
return 0
else
if [ "$1" = -v ]; then
echo "I am not being sourced, my script/shell name was $0"
fi
return 1
fi
}
if am_I_sourced -v; then
echo "Do something with sourced script"
else
echo "Do something with executed script"
fi
现在尝试以多种方式执行它:
~/toys/bash $ chmod a+x include2.sh
~/toys/bash $ ./include2.sh
I am not being sourced, my script/shell name was ./include2.sh
Do something with executed script
~/toys/bash $ bash ./include2.sh
I am not being sourced, my script/shell name was ./include2.sh
Do something with executed script
~/toys/bash $ . include2.sh
I am being sourced, this filename is include2.sh and my caller script/shell name was bash
Do something with sourced script
因此,这毫无例外地有效,并且它没有使用易碎的$_东西。此技巧使用BASH的自省功能,即内置变量FUNCNAME和BASH_SOURCE; 请参阅bash手册页中的文档。
只有两个警告:
1)调用am_I_called 必须发生在被执行的脚本,但没有在任何功能,以免${FUNCNAME[1]}返回别的东西。是的...您本可以检查${FUNCNAME[2]}-但您只会让生活变得更艰难。
2)如果要找出要包含的文件名,函数am_I_called 必须驻留在源脚本中。
我想对丹尼斯的非常有帮助的答案提出一些小建议,以使其更便于携带,我希望:
[ "$_" != "$0" ] && echo "Script is being sourced" || echo "Script is a subshell"
因为[[(未被肛门保留的恕我直言)Debian POSIX兼容外壳无法识别dash。同样,在所述外壳中,可能还需要引号来防止包含空格的文件名。
$_很脆。您必须先将其检查为脚本中的第一件事。即使这样,也不能保证包含外壳程序的名称(如果已获取)或脚本的名称(如果已执行)。
例如,如果用户设置了BASH_ENV,则在脚本顶部$_包含脚本中最后执行的命令的名称BASH_ENV。
我发现最好的方法是这样使用$0:
name="myscript.sh"
main()
{
echo "Script was executed, running main..."
}
case "$0" in *$name)
main "$@"
;;
esac
不幸的是,由于zsh的功能functionargzero超出了其名称所建议的范围,并且默认情况下处于启用状态,因此无法在zsh中立即使用。
为解决此问题,我放入unsetopt functionargzero了.zshenv。
我遵循mklement0紧凑表达式。
很好,但是我注意到在以这种方式调用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
随时添加奇异的shell支持:)
ksh 93+u,ksh ./myscript.sh对我来说效果很好(带有我的声明)-您使用的是哪个版本?
/proc/$$/cmdline)且dash仅专注于(sh例如,在Ubuntu上也是如此)。如果您愿意做出某些假设,则可以检查$0一种可移植的合理(但不完整)的测试。
sh/ 的最佳便携式近似值dash。
我认为ksh和bash中都没有任何可移植的方式来执行此操作。在bash中,您可以使用caller输出检测到它,但是我不认为ksh中存在等效的东西。
$0工程bash,ksh93和pdksh。我不必ksh88测试。
直截了当:要评估变量“ $ 0”是否等于命令行管理程序的名称。
像这样:
#!/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,因此不安全),您应该:
这两个选项都无法自动缩放,但这是更安全的方法。
例如:
当您通过SSH会话获取脚本时,变量“ $ 0”(使用source时)返回的值为-bash。
#!/bin/bash
echo "First Parameter: $0"
echo
if [[ "$0" == "bash" || "$0" == "-bash" ]] ; then
echo "The script was sourced."
else
echo "The script WAS NOT sourced."
fi
要么
#!/bin/bash
echo "First Parameter: $0"
echo
if [[ "$0" == "bash" ]] ; then
echo "The script was sourced."
elif [[ "$0" == "-bash" ]] ; then
echo "The script was sourced via SSH session."
else
echo "The script WAS NOT sourced."
fi
/bin/bash -c '. ./check_source.sh'给The script WAS NOT sourced.。同样的错误:ln -s /bin/bash pumuckl; ./pumuckl -c '. ./check_source.sh'->The script WAS NOT sourced.
我最后检查了一下 [[ $_ == "$(type -p "$0")" ]]
if [[ $_ == "$(type -p "$0")" ]]; then
echo I am invoked from a sub shell
else
echo I am invoked from a source command
fi
当用于curl ... | bash -s -- ARGS实时运行远程脚本时,在运行实际脚本文件时,$ 0只是bash正常的/bin/bash,所以我type -p "$0"用来显示bash的完整路径。
测试:
curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath | bash -s -- /a/b/c/d/e /a/b/CC/DD/EE
source <(curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath)
relpath /a/b/c/d/e /a/b/CC/DD/EE
wget https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath
chmod +x relpath
./relpath /a/b/c/d/e /a/b/CC/DD/EE
这是从其他一些答案中衍生出来的,涉及“通用”跨壳支持。诚然,这与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只是在确保此机制可以扩展到子进程中使用。