要么我在这里问的是极其非正统/非常规/有风险的,要么我的谷歌技能根本达不到标准......
在bash
shell 脚本中,是否有任何简单的方法可以判断它是由另一个 shell 脚本获取的,还是由其自身运行的?换句话说,是否可以区分以下两种行为?
# from another shell script
source myScript.sh
# from command prompt, or another shell script
./myScript.sh
我正在考虑做的是创建一个类似实用程序的 shell 脚本,其中包含bash
在获取源代码时可以使用的函数。不过,当该脚本自行运行时,我希望它也根据定义的函数执行某些操作。这个 shell 脚本是否可以获取某种环境变量,例如
some_function() {
# ...
}
if [ -z "$IS_SOURCED" ]; then
some_function;
fi
最好,我正在寻找一种不需要调用者脚本设置任何标志变量的解决方案。
编辑:我知道获取脚本和运行脚本之间的区别,如果可以的话我想在这里找出什么分辨其中的不同之处在正在使用的脚本中(以两种方式)。
答案1
是的 - $0 变量给出了脚本运行时的名称:
$ cat example.sh
#!/bin/bash
script_name=$( basename ${0#-} ) #- needed if sourced no path
this_script=$( basename ${BASH_SOURCE} )
if [[ ${script_name} = ${this_script} ]] ; then
echo "running me directly"
else
echo "sourced from ${script_name}"
fi
$ cat example2.sh
#!/bin/bash
. ./example.sh
运行如下:
$ ./example.sh
running me directly
$ ./example2.sh
example.sh sourced from example2.sh
这并不适合从交互式 shell 获取源代码,但您会明白这个想法(我希望)。
已更新以包含 BASH_SOURCE - 感谢 hjk
答案2
组合@黑暗之心使用环境变量回答BASH_SOURCE
似乎可以解决问题:
$ head example*.sh
==> example2.sh <==
#!/bin/bash
. ./example.sh
==> example.sh <==
#!/bin/bash
if [ "$(basename $0)" = "$(basename $BASH_SOURCE)" ]; then
echo "running directly"
else
echo "sourced from $0"
fi
$ ./example2.sh
sourced from ./example2.sh
$ ./example.sh
running directly
编辑BASH_SOURCE
如果我仅计算数组中元素的数量,这似乎是一个更简单的解决方案:
if [ ${#BASH_SOURCE[@]} -eq 1 ]; then echo "running directly"; else echo "sourced from $0"; fi
答案3
我刚刚创建了与 BusyBox 类似的相同类型的库脚本。在其中,我使用以下函数来测试它是否正在被获取......
function isSourced () {
[[ "${FUNCNAME[1]}" == "source" ]] && return 0
return 1
}
Bash 维护的 FUNCNAME 数组本质上是一个函数调用堆栈。$FUNCNAME
(或${FUNCNAME[0]}
) 是当前正在执行的函数的名称。${FUNCNAME[1]}
是调用它的函数的名称,等等。
最上面的项目是脚本本身的特殊值。它将包含...
- 如果脚本是来源的,则“来源”一词
- 如果正在执行脚本并且测试是在函数内完成的,则为“main”一词
- ""(null) 如果正在执行脚本并且测试是在任何函数之外完成的,即......在脚本本身的级别。
上面的函数实际上仅在脚本级别调用时才有效(这就是我所需要的)。如果从另一个函数内部调用它将会失败,因为数组项编号将是错误的。要使其在任何地方都能工作,需要找到堆栈顶部并测试该值,这比较复杂。
如果您需要的话,您可以使用以下命令获取“堆栈顶部”的数组项编号...
local _top_of_stack=$(( ${#FUNCNAME[@]} - 1 ))
${#FUNCNAME[@]}
是数组中的项目数。作为从零开始的数组,我们减去 1 以获得最后一项#。
这三个函数一起使用来生成类似于 Python 的函数堆栈跟踪,它们可能会让您更好地了解这一切是如何工作的......
function inspFnStack () {
local T+=" "
local _at=
local _text="\n"
local _top=$(inspFnStackTop)
local _fn=${FUNCNAME[1]}; [[ $_fn =~ source|main ]] || _fn+="()"
local i=_top; ((--i))
#
_text+="$i item function call stack for $_fn ...\n"
_text+="| L BASH_SOURCE{BASH_LINENO called from}.FUNCNAME \n"
_text+="| ---------------------------------------------------\n"
while (( $i > 0 ))
do
_text+="| $i ${T}$(inspFnStackItem $i)\n"
T+=" "
((--i))
done
#
printf "$_text\n"
#
return 0
}
function inspFnStackItem () {
local _i=$1
local _fn=${FUNCNAME[$_i]}; [[ $_fn =~ source|main ]] || _fn+="()"
local _at="${BASH_LINENO[$_i-1]}"; [[ $_at == 1 ]] && _at="trap"
local _item="${BASH_SOURCE[$_i]}{${_at}}.$_fn"
#
printf "%s" "$_item"
return 0
}
function inspFnStackTop () {
# top stack item is 1 less than length of FUNCNAME array stack
printf "%d\n" $(( ${#FUNCNAME[@]} - 1 ))
#
return 0
}
请注意,FUNCNAME、BASH_SOURCE 和 BASH_LINENO 是 bash 维护的 3 个数组,就好像它们是一个三维数组一样。
答案4
只是想补充一点,对数组进行计数似乎是不可靠的,并且可能不应该假设source
使用了点(),因为使用点(.
)也很常见(并且早于source
关键字)。
例如,对于sourced.sh
仅包含以下内容的脚本echo $0
:
$ . sourced.sh
bash
$ source sourced.sh
bash
$ chmod +x sourced.sh
$ ./sourced.sh
./sourced.sh
$ cat ./sourced.sh
echo $0
建议的比较解决方案效果更好。