我有一个脚本,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=0
ksh(已在93u +上验证)
[[ $(cd "$(dirname -- "$0")" &&
printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] &&
sourced=1 || sourced=0
zsh(在5.0.5上验证)-确保在函数外部调用此函数
[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0
跨壳(bash,ksh,zsh)
([[ -n $ZSH_EVAL_CONTEXT && $ZSH_EVAL_CONTEXT =~ :file$ ]] ||
[[ -n $KSH_VERSION && $(cd "$(dirname -- "$0")" &&
printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] ||
[[ -n $BASH_VERSION ]] && (return 0 2>/dev/null)) && sourced=1 || sourced=0
POSIX兼容 ; 不是一个班轮(单管)因技术原因而无法完全健壮(参见下图):
sourced=0
if [ -n "$ZSH_EVAL_CONTEXT" ]; then
case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
elif [ -n "$KSH_VERSION" ]; then
[ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ] && sourced=1
elif [ -n "$BASH_VERSION" ]; then
(return 0 2>/dev/null) && sourced=1
else # All other shells: examine $0 for known shell binary filenames
# Detects `sh` and `dash`; add additional shell filenames as needed.
case ${0##*/} in sh|dash) sourced=1;; esac
fi
说明:
(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
脚本时会产生误报。例如,因为是那么就,而是完整路径。虽然你通常不会使用这一技术在调用的脚本-你会只是调用它们直接() -它是在与结合有用的调试。bash
bash my-script
$0
my-script
$BASH_SOURCE
$PATH
my-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/…)。该dash
shell对一个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
可以在功能之外使用。但是,相反的内容(仅适用于函数)均未记录。可以观察到的是,它可以正常工作(通过bash
v4.3和bash
v4.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 3
bash -xc '. script' "`which script`" 1 2 3
bash 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 | $SHELL
bash
的病历足够近时,这才是正确的。据报道,此食谱在某些型号上失败了。因此,请务必检查它是否适合您的情况。)但是,我无法考虑您真正需要它的任何真正原因,也无法考虑并行获取完全相同的脚本的能力!通常,您可以将其包装起来以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
只是在确保此机制可以扩展到子进程中使用。