如何检测脚本是否被find
我有一个脚本,我不希望它调用exit
如果它是来源。 最初,我虽然检查$0 == bash
但如果脚本来自另一个脚本,或者如果用户从ksh
源它,这有问题。 是否有一个可靠的方法来检测脚本是否来源?
这在Bash和Korn之间似乎是可移植的:
[[ $_ != $0 ]] && echo "Script is being sourced" || echo "Script is a subshell"
一个类似于这样的代码行或者像`pathname =“$ _”(后面的testing和操作)的任务必须位于脚本的第一行或shebang之后的行(如果使用的话,应该是ksh为了在大多数情况下工作)。
如果您的Bash版本知道BASH_SOURCE数组variables,请尝试如下所示:
# 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 ..."
在阅读@ 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
而不是.
为了可读性(as是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
现在我们可以testing:
./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
标志之前testing脚本:
bash ./testscript proc: 25776[ppid:24890] is own (DW purpose: sourced)
或者使用预定义的variables:
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很多,在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
在源代码中有一些variables是遗漏的,但是没有什么关系。
你甚至可以检查$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
,但这只适用于子级别的一个级别…
对不起,在ksh下我找不到一个可靠的方法。
为bash
, ksh
, zsh
提供了强大的解决scheme ,包括一个跨shell的 解决scheme ,以及一个相当强大的符合POSIX标准的解决scheme :
-
给出的版本号码是validationfunction的版本号码,可能这些解决scheme也适用于更早的版本, 欢迎提供反馈意见 。
-
仅使用POSIXfunction (例如在
dash
,在Ubuntu上充当/bin/sh
), 没有可靠的方法来确定脚本是否来源 – 请参阅此答案的底部以获得最佳近似值 。
单行遵循 – 下面的解释; 跨壳版本是复杂的,但它应该强健地工作:
-
bash (在3.57validation)
[[ $0 != "$BASH_SOURCE" ]] && sourced=1 || sourced=0
-
ksh (在93u +validation)
[[ $(cd "$(dirname -- "$0")" && printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] && sourced=1 || sourced=0
-
zsh (在5.0.5validation) – 一定要在函数之外调用它
[[ $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 && $0 != "$BASH_SOURCE" ]]) && sourced=1 || sourced=0
-
[不鲁棒]仅使用POSIXfunction :请参阅底部
说明:
庆典
[[ "$0" != "$BASH_SOURCE" ]] && sourced=1 || sourced=0
-
$BASH_SOURCE
… ALWAYS包含脚本文件参数,无论是否来源。 -
$0
…- 如果没有来源: 总是与
$BASH_SOURCE
相同 - 如果来源:
- 如果源自另一个脚本:脚本来自哪个脚本。
- 如果来源交互:
- 通常情况下,
bash
为非loginshell,-bash
为loginshell(例如在OSX上),或者,如果Bash被调用为sh
,则类似于sh
或-sh
。 - 但是,如果Bash以相对或绝对path(直接)开始,那么这个path将反映在
$0
。 - 还要注意,可以用
$0
的任意值启动Bash(或任何程序),比如通过使用exec
内build和-a
选项。
- 通常情况下,
- 如果没有来源: 总是与
KSH
[[ \ $(cd "$(dirname -- "$0")" && printf '%s' "${PWD%/}/")$(basename -- "$0") != \ "${.sh.file}" \ ]] && sourced=1 || sourced=0
特殊variables${.sh.file}
有点类似于$BASH_SOURCE
; 请注意, ${.sh.file}
在bash,zsh和dash中会导致语法错误 ,因此请务必在多shell脚本中有条件地执行它。
与bash不同的是, $0
和${.sh.file}
不能保证在非源代码中完全一样,因为$0
可能是一个相对path,而${.sh.file}
总是一个完整path,所以比较之前,必须将$0
parsing为完整path。
zsh的
[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0
$ZSH_EVAL_CONTEXT
包含关于评估上下文的信息 – 在函数之外调用它。 在源脚本[的顶级范围]内, $ZSH_EVAL_CONTEXT
以:file
结尾 。
警告:在一个命令replace中,zsh追加:cmdsubst
,所以testing$ZSH_EVAL_CONTEXT
为:file:cmdsubst$
there。
仅使用POSIXfunction
如果您愿意做出某些假设,您可以基于知道可能正在执行脚本的shell的文件名,对脚本是否来源进行合理的猜测 ( 但不是傻瓜式的猜测) 。
值得注意的是,这意味着如果你的脚本被另一个脚本来源,这种方法失败。
本答案中的“如何处理源调用”一节讨论了POSIXfunction无法处理的边缘情况。
这依赖于$0
的标准行为,例如zsh
并没有performance出来。
因此,最安全的方法是将上面强大的特定于shell的方法与其余所有shell 的后备解决scheme相结合 。
( StéphaneDesneux)和他的灵感答案 (将我的跨壳声明expression式转换为sh
– compatible声明并为其他声明添加处理程序)。
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 [ "$0" != "$BASH_SOURCE" ] && 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
BASH_SOURCE[]
答案(bash-3.0及更高版本)似乎最简单,虽然BASH_SOURCE[]
没有logging在函数体外工作 (它现在正常工作,不同意手册页)。
正如Wirawan Purwanto所build议的那样,最强大的方法是在函数中检查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[]
可以保存捕获和parsingcaller
输出。 您需要知道或计算您的本地通话深度是正确的。 像另一个函数或脚本内源脚本的情况下会导致数组(堆栈)更深。 ( FUNCNAME
是一个特殊的bash数组variables,只要它不被unset
,它就应该有与调用栈相对应的连续索引。)
function issourced() { [[ ${FUNCNAME[@]: -1} == "source" ]] }
(在bash-4.2及更高版本中,您可以使用简单的forms${FUNCNAME[-1]}
来代替数组中的最后一项。由于下面的Dennis Williamson的注释,改进和简化。
然而,你所说的问题是“ 我有一个脚本,我不希望它叫'退出',如果它是来源 ”。 这种情况常见的bash
成语是:
return 2>/dev/null || exit
如果脚本正在发送,则return
将终止源脚本并返回给调用者。
如果脚本正在执行,则return
将返回一个错误(redirect), exit
将正常结束脚本。 如果需要, return
和exit
都可以退出代码。
可悲的是,这在ksh
不起作用(至less不是在AT&T衍生版本中),它将return
视为等价于在函数或点源脚本之外调用时exit
。
更新 :在当代版本的ksh
你可以做的是检查设置为函数调用深度的特殊variables.sh.level
。 对于一个被调用的脚本,它最初将被取消设置,对于一个点源脚本,它将被设置为1。
function issourced { [[ ${.sh.level} -eq 2 ]] } issourced && echo this script is sourced
这不像bash版本那么强大,你必须在你正在testing的文件的顶层或者已知函数深度调用issourced()
。
(你也可能对github上的代码感兴趣,它使用ksh
纪律函数和一些debugging陷阱欺骗来模拟bash FUNCNAME
数组。
这个规范的答案在这里: http : //mywiki.wooledge.org/BashFAQ/109也提供了$-
作为壳状态的另一个指标(尽pipe不完美)。
笔记:
- 可以创build名为“main”和“source”的bash函数( 覆盖内build函数),这些名称可以出现在
FUNCNAME[]
但只要testing该数组中的最后一项,就没有歧义。 - 我没有一个好的答案
pdksh
。 我能find的最接近的东西只适用于pdksh
,其中脚本的每个源打开一个新的文件描述符(从原始脚本开始)。 几乎肯定不是你想要的东西…
TL; DR
尝试执行一个return
语句。 如果脚本没有find,那会引发错误。 您可以捕获该错误,并根据需要进行处理。
把它放在一个文件中,然后调用它,比如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中。
说明
如果您尝试在函数之外执行它,或者脚本未find,则return
语句将引发错误。 从shell提示符下试试这个:
shell-prompt> return output: ...can only `return` from a function or sourced script
您不需要看到该错误消息,因此您可以将输出redirect到dev / null:
shell-prompt> return >/dev/null 2>&1
现在检查退出代码。 0表示正常(没有错误发生),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
我将给出一个BASH特定的答案。 科恩壳,对不起。 假设你的脚本名是include2.sh
; 然后在include2.sh
创build一个名为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的内省function,即内置的variablesFUNCNAME
和BASH_SOURCE
; 请参阅bash手册页面中的文档。
只有两个警告:
1)对am_I_called
的调用必须 在源脚本中进行,但不能在任何函数中进行,以免${FUNCNAME[1]}
返回其他内容。 是啊…你可以检查${FUNCNAME[2]}
– 但你只是让你的生活更难。
2)函数am_I_called
必须驻留在源脚本中,如果你想知道被包含文件的名字是什么的话。
我想build议对丹尼斯的非常有帮助的回答进行一个小小的更正,让它更轻便一点,我希望:
[ "$_" != "$0" ] && echo "Script is being sourced" || echo "Script is a subshell"
因为[[
不被有些肛门保留的IMHO)Debian POSIX兼容 shell, dash
识别。 此外,可能还需要引号来防止包含空格的文件名,再次在所述shell中。
这在稍后的脚本中起作用,并且不依赖于_variables:
## 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
$_
非常脆弱。 你必须把它作为你在脚本中做的第一件事来检查。 即使如此,也不保证包含你的shell的名字(如果有的话)或者脚本的名字(如果被执行的话)。
例如,如果用户已经设置了BASH_ENV
,那么在脚本的顶部, $_
包含在BASH_ENV
脚本中执行的最后一个命令的名称。
我发现的最好的方法是使用$0
像这样:
name="myscript.sh" main() { echo "Script was executed, running main..." } case "$0" in *$name) main "$@" ;; esac
不幸的是,这种方式在zsh中并不适用,因为functionargzero
选项的functionargzero
超过了它的名字,并且默认情况下处于打开状态。
要解决这个问题,我把我的.zshenv
。
我跟着mklement0压缩expression式 。
这是整洁的,但我注意到,它可以失败的情况下ksh时调用这样的:
/bin/ksh -c ./myscript.sh
(它认为它是来源,这不是因为它执行一个子shell)但expression式将工作来检测:
/bin/ksh ./myscript.sh
而且,即使expression式是紧凑的,语法也不兼容所有的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
随意添加异国情调的炮弹支持:)
直接点:您必须评估variables“$ 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 check_source.sh First Parameter: bash The script was sourced.
很难有一个100%的可移植的方式来检测脚本是否来源。
关于我的经验(7年Shellscripting) ,唯一安全的方法(不依赖于PID等环境variables,由于它是VARIABLE而不安全),您应该:
- 从你的if扩展可能性
- 使用开关/shell,如果你想。
这两个选项都不能自动缩放,但这是更安全的方法。
例如:
当您通过SSH会话获取脚本时,variables“$ 0” (使用源代码 )返回的值是-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
我不认为在ksh和bash中都有这样的可移植的方法。 在bash中,你可以使用caller
输出来检测它,但是我不认为在ksh中存在等价物。
我需要一个在bash.version> = 3的[mac,linux]上运行的单线程,这些答案都不符合要求。
[[ ${BASH_SOURCE[0]} = $0 ]] && main "$@"