我有一个脚本,我不希望它调用退出,如果它是来源。
我想检查是否$0 == bash,但这有问题,如果脚本是从另一个脚本,或者如果用户从不同的shell,如ksh源。
是否有一种可靠的方法来检测脚本是否被引用?
我有一个脚本,我不希望它调用退出,如果它是来源。
我想检查是否$0 == bash,但这有问题,如果脚本是从另一个脚本,或者如果用户从不同的shell,如ksh源。
是否有一种可靠的方法来检测脚本是否被引用?
当前回答
解决这个问题的方法不是编写需要知道这些事情才能正确运行的代码。做到这一点的方法是将代码放入函数中,而不是放入需要源代码的脚本主线中。
函数内部的代码可以只返回0或1。这只终止了函数,因此控制返回到调用该函数的任何对象。
无论从源脚本的主线调用函数,还是从顶级脚本的主线调用函数,或者从另一个函数调用函数,都是如此。
使用sourcing来引入“库”脚本,这些脚本只定义函数和变量,但实际上不执行任何其他顶级命令:
. path/to/lib.sh # defines libfunction
libfunction arg
否则:
path/to/script.sh arg # call script as a child process
而不是:
. path/to/script.sh arg # shell programming anti-pattern
其他回答
这似乎在Bash和Korn之间是可移植的:
[[ $_ != $0 ]] && echo "Script is being sourced" || echo "Script is a subshell"
与此类似的行或赋值语句如pathname="$_"(带有稍后的测试和操作)必须位于脚本的第一行或shebang之后的行(如果使用了shebang,则应该用于ksh,以便它在大多数情况下工作)。
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")'
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.)
function issourced() {
[[ ${FUNCNAME[@]: -1} == "source" ]]
}
(在bash-4.2及以后版本中,可以使用更简单的形式${FUNCNAME[-1]}来代替数组中的最后一项。感谢Dennis Williamson下面的评论,改进和简化了。)
然而,你的问题如所述是“我有一个脚本,我不希望它调用'退出',如果它是来源”。这种情况下常见的bash习惯用法是:
return 2>/dev/null || exit
如果脚本是源脚本,则return将终止源脚本并返回给调用者。
如果脚本正在执行,那么return将返回一个错误(重定向),exit将正常终止脚本。如果需要,return和exit都可以使用退出码。
遗憾的是,这在ksh中不起作用(至少在我这里的AT&T派生版本中不起作用),如果在函数或点源脚本外部调用return,它将return视为等同于exit。
更新:在当前版本的ksh中,您可以检查特殊变量.sh。设置为函数调用深度的级别。对于一个被调用的脚本,它最初将被取消设置,对于一个点源脚本,它将被设置为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还提供了$-作为shell状态的另一个指示器(尽管并不完善)。
注:
可以创建名为“main”和“source”的bash函数(覆盖内置),这些名称可能出现在FUNCNAME[]中,但只要只测试该数组中的最后一项,就不会有歧义。 我对pdksh没有一个好的答案。我能找到的最接近的方法只适用于pdksh,其中脚本的每个源都打开一个新的文件描述符(原始脚本从10开始)。几乎肯定不是你想要依靠的东西……
我将给出一个特定于bash的答案。Korn shell,对不起。假设脚本名为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必须驻留在源脚本中,如果你想知道被包含的文件的名称。
这在后面的脚本中起作用,不依赖于_变量:
## Check to make sure it is not sourced:
Prog=myscript.sh
if [ $(basename $0) = $Prog ]; then
exit 1 # not sourced
fi
or
[ $(basename $0) = $Prog ] && exit
看了@DennisWilliamson的回答后,有一些问题,如下所示:
因为这个问题代表ksh和bash,所以这个答案中还有一个关于ksh的部分…见下文。
简单的bash方式
[ "$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。对于可读性(如。是源的别名):
. <(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
更难:ksh现在…
由于我不经常使用ksh,在阅读了一些手册页后,有我的尝试:
#!/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,但这是为了确保只有手工来源的情况……
你甚至可以试着检查parent是什么:
把它放在你的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下。