我有一个脚本,如果它被源引用,我不想让它调用exit
。
我考虑检查$0 == bash
,但是如果脚本从另一个脚本中被源引用,或者用户从不同的shell(如ksh
)中源引用它,则存在问题。
有没有一种可靠的方法来检测脚本是否正在被源引用?
我真的认为这是最美观的做法:
从我的if__name__==__main___check_if_sourced_or_executed_best.sh
文件中,可以找到一个更好的实现方式。
我的eRCaGuy_hello_world代码库也包含这个文件。
#!/usr/bin/env bash
main() {
echo "Running main."
# Add your main function code here
}
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
# This script is being run.
__name__="__main__"
else
# This script is being sourced.
__name__="__source__"
fi
# Only run `main` if this script is being **run**, NOT sourced (imported)
if [ "$__name__" = "__main__" ]; then
echo "This script is being run."
main
else
echo "This script is being sourced."
fi
参考资料:
if __name__ == '__main__'
的是什么?"${BASH_SOURCE[0]}" = "$0"
。如果你愿意,你还可以探索以下替代方法,但我更喜欢使用上面的代码块。
重要提示: 使用"${FUNCNAME[-1]}"
技术不适用于处理嵌套脚本,其中一个脚本调用或源另一个脚本,而if [ "${BASH_SOURCE[0]}" = "$0" ]
技术则完全适用。这是使用if [ "${BASH_SOURCE[0]}" = "$0" ]
的另一个重要原因。
我已经阅读了各种回答并结合其他问题,总结出了4种方法,并将它们放在同一个地方以便概括。
if __name__ == "__main__":
请参考Python中 if __name__ == "__main__": 的用法。
main
函数、自动检测执行 vs 源(类似于 Python 中的 if __name__ == "__main__":
)等功能。请查看我在这里列出的展示/模板程序(链接在此处),当前称为argument_parsing__3_advanced__gen_prog_template.sh
,但如果名称在将来发生更改,我将在上面链接的列表中更新它。无论如何,这里是 4 种 Bash 技术:
Technique 1 (can be placed anywhere; handles nested scripts): See: https://unix.stackexchange.com/questions/424492/how-to-define-a-shell-script-to-be-sourced-not-run/424495#424495
if [ "${BASH_SOURCE[0]}" -ef "$0" ]; then
echo " This script is being EXECUTED."
run="true"
else
echo " This script is being SOURCED."
fi
Technique 2 [My favorite technique] (can be placed anywhere; handles nestes scripts): See this type of technique in-use in my most-advanced bash demo script yet, here: argument_parsing__3_advanced__gen_prog_template.sh, near the bottom.
Modified from: What is the bash equivalent to Python's `if __name__ == '__main__'`?
if [ "${BASH_SOURCE[0]}" == "$0" ]; then
echo " This script is being EXECUTED."
run="true"
else
echo " This script is being SOURCED."
fi
Technique 3 (requires another line which MUST be outside all functions): Modified from: How to detect if a script is being sourced
# A. Place this line OUTSIDE all functions:
(return 0 2>/dev/null) && script_is_being_executed="false" || script_is_being_executed="true"
# B. Place these lines anywhere
if [ "$script_is_being_executed" == "true" ]; then
echo " This script is being EXECUTED."
run="true"
else
echo " This script is being SOURCED."
fi
Technique 4 [Limitation: does not handle nested scripts!] (MUST be inside a function):
Modified from: How to detect if a script is being sourced
and Unix & Linux: How to define a shell script to be sourced not run.
if [ "${FUNCNAME[-1]}" == "main" ]; then
echo " This script is being EXECUTED."
run="true"
elif [ "${FUNCNAME[-1]}" == "source" ]; then
echo " This script is being SOURCED."
else
echo " ERROR: THIS TECHNIQUE IS BROKEN"
fi
${FUNCNAME[-1]}
trick: @mr.spuratic: How to detect if a script is being sourced - he learned it from Dennis Williamson apparently.$_
非常脆弱。在脚本中的第一件事就是要检查它。即使这样做了,也不能保证包含你的Shell名称(如果被引用)或脚本名称(如果被执行)。
例如,如果用户设置了BASH_ENV
,那么在脚本顶部,$_
包含上一个在BASH_ENV
脚本中执行的命令的名称。
我发现最好的方法是像这样使用$0
:
name="myscript.sh"
main()
{
echo "Script was executed, running main..."
}
case "$0" in *$name)
main "$@"
;;
esac
很遗憾,在zsh中,由于functionargzero
选项默认开启且具有更多功能,这种方法无法直接使用。
为了解决这个问题,我在我的.zshenv
文件中添加了unsetopt functionargzero
命令。
虽然不完全符合OP的要求,但我经常需要引用脚本来加载其函数(即作为库),例如用于基准测试或测试目的。
以下是适用于所有shell的设计(包括POSIX):
run_main()
函数中。--no-run
参数,该参数不会执行任何操作;如果没有--no-run
,则可以调用run_main
。source
脚本:set -- --no-run "$@"
. script.sh
shift
< p >使用 .
或 source
的问题在于无法便携地传递参数给脚本。 POSIX shell忽略 .
的参数并始终传递调用者的"$@"
。< /p >
我按照mklement0紧凑表达式的方式进行了操作。
这很不错,但我注意到在以下情况下,它可能会在ksh中失败:
/bin/ksh -c ./myscript.sh
它认为自己有来源,但实际上并没有,因为它执行了一个子shell。但以下表达式可以用来检测这个问题:
/bin/ksh ./myscript.sh
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
对我来说很好用(与我的语句一起)- 你使用的是哪个版本? - mklement0/proc/$$/cmdline
),并且仅关注于dash
(在Ubuntu上也充当sh
) 。如果您愿意做出某些假设,可以检查$0
以进行合理但不完整的测试,这是一种可移植的方法。 - mklement0sh
/ dash
的情况,并在我的答案中添加了附录。 - mklement0${0##*/}
和$()
不可移植至至少一个旧的Bourne shell(我仍然必须使用它,即使这是必要的,这是一种耻辱):echo ${0##*/}
会产生“bad substitution”的结果,而echo $(dirname $0)
则会出现syntax error: '(' unexpected
。echo $0
会输出-sh
。 - kbulgrien对于@mklement0的答案,我有一个小小的补充。这是我在脚本中使用的自定义函数,用于确定它是否被来源:
replace_shell(){
if [ -n "$ZSH_EVAL_CONTEXT" ]; then
case $ZSH_EVAL_CONTEXT in *:file*) echo "Zsh is sourced";; esac
else
case ${0##*/} in sh|dash|bash) echo "Bash is sourced";; esac
fi
}
"$ZSH_EVAL_CONTEXT"
的输出是toplevel:file:shfunc
而不仅仅是toplevel:file
,因此*:file*
可以解决这个问题。请注意保留HTML标签。0
或1
。这样只会终止函数的执行,控制权就会返回到调用该函数的地方。. 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
我认为在ksh和bash中没有任何可移植的方法来做到这一点。 在bash中,您可以使用caller
输出进行检测,但我不认为ksh中存在等效物。
$0
在 bash
、ksh93
和 pdksh
中可用。我没有 ksh88
进行测试。 - Mikel我最终检查了[[ $_ == "$(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
executingName="${0##*/}"
separator='+'
SHS="${separator?}sh${separator?}dash${separator?}-sh${separator?}-dash${separator?}"
sourced="${BASH_VERSION:+$(
( 2>/dev/null return 0 ) || test 0 -eq ${??} ;
)${??
}}${ZSH_VERSION:+$(
test "${ZSH_EVAL_CONTEXT##*:file}" != "${ZSH_EVAL_CONTEXT-}" ;
)${??
}}${KSH_VERSION:+$(
test "${executingName?
}" = "${0-}" -o "${executingName?
}" != "${.sh.file##*/}"
)${??}}$(
test "${SHS#*${separator?}${executingName?
}${separator?}}" != "${SHS?
}" && printf '%s' "${??}"
)"
printf '%9s' "$(
test 0 -ne ${sourced:-8} && printf "unsourced" || printf "sourced"
)"
THIS_FILE="$(lsof | grep '^'$$ | tail -n1 | awk '{print $3}')"
[ "${0##*/}" != "${THIS_FILE##*/}" ] && sourced='yes' || sourced='no'