在Bash中,我希望能够同时源代码和执行文件。Bash与Python的if __name__ == '__main__'
等价于什么
我在Stackoverflow上没有找到关于这个主题的现成问题/解决方案(我怀疑我的提问方式与现有的问题/答案不匹配,但由于我的Python经验,这是我能想到的最明显的表达方式)。
p.s.关于可能重复的问题(如果我有更多的时间,我会写一个更短的回复):
链接到的问题问"如何检测脚本是否来源",但这个问题问"你如何创建一个既可以来源又可以作为脚本运行的bash脚本?"。这个问题的答案可能使用前一个问题的某些方面,但有以下额外要求/问题:
- 一旦检测到脚本的来源,最好的方法是不运行脚本(并避免意外的副作用(除了导入感兴趣的函数),如添加/删除/修改环境/变量)
- 一旦你检测到脚本正在运行而不是源代码,实现脚本的规范方式是什么(把它放在函数中?或者可能只是放在if语句之后?如果你把它放到if语句之后,它会有副作用吗
- 我在Bash上发现的大多数谷歌搜索都没有涵盖这个主题(一个既可以来源又可以执行的Bash脚本)实现这一点的规范方法是什么?这个话题是因为不鼓励还是不好做而没有被涵盖?有gotchas吗
解决方案:
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
我添加这个答案是因为我想要一个以模仿Python的if __name__ == '__main__'
但使用Bash的风格编写的答案。
关于BASH_SOURCE
与$_
的用法。我使用BASH_SOURCE
是因为它看起来比$_
(链接1,链接2)更健壮。
下面是我用两个Bash脚本测试/验证的一个示例。
带xyz()
函数的script1.sh:
#!/bin/bash
xyz() {
echo "Entering script1's xyz()"
}
main() {
xyz
echo "Entering script1's main()"
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
script2.sh尝试调用函数xyz()
:
#!/bin/bash
source script1.sh
xyz
使用FUNCNAME
FUNCNAME
是一个仅在函数中可用的数组,其中FUNCNAME[0]
是函数的名称,FUNCNAME[1]
是调用方的名称。因此,对于顶级函数,在执行的脚本中FUNCNAME[1]
将是main
,或者在源脚本中是source
。
#!/bin/bash
get_funcname_1(){
printf '%sn' "${FUNCNAME[1]}"
}
_main(){
echo hello
}
if [[ $(get_funcname_1) == main ]]; then
_main
fi
示例运行:
$ bash funcname_test.sh
hello
$ source funcname_test.sh
$ _main
hello
我不知道我是怎么偶然发现的。manbash没有提到source
值。
替代方法:在函数外使用FUNCNAME[0]
这只适用于4.3和4.4,没有记录。
if [[ ${FUNCNAME[0]} == main ]]; then
_main
fi
没有。我通常使用这个:
#!/bin/bash
main()
{
# validate parameters
echo "In main: $@"
# your code here
}
main "$@"
如果你想知道这个脚本是否是source
'd,只需将你的main
调用包装在中即可
if [[ "$_" != "$0" ]]; then
echo "Script is being sourced, not calling main()"
else
echo "Script is a subshell, calling main()"
main "$@"
fi
参考:如何检测脚本是否来源于
return 2> /dev/null
要获得一种惯用的Bash方法,可以使用return
,如下所示:
_main(){
echo hello
}
# End sourced section
return 2> /dev/null
_main
示例运行:
$ bash return_test.sh
hello
$ source return_test.sh
$ _main
hello
如果脚本是来源的,return
将返回到父级(当然),但如果脚本被执行,return
将产生一个隐藏的错误,脚本将继续执行。
我已经在GNUBash 4.2到5.0上测试过了,这是我的首选解决方案。
警告:这在大多数其他shell中都不起作用。
这是基于mr.spuratic关于如何检测脚本是否来源的部分答案
我真的认为这是实现Bash:中等效if __name__ == "__main__"
的最Python和最漂亮的方法
来自我的eRCaGuy_hello_world repo:中的hello_world_best.sh
#!/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
main "$@"
fi
要运行它,请运行./hello_world_best.sh
。以下是运行和输出示例:
eRCaGuy_hello_world/bash$ ./hello_world_best.sh
Running main.
要获取(导入)它,请运行. ./hello_world_best.sh
、. hello_world_best.sh
或source hello_world_best.sh
等。当您获取它时,在这种情况下不会看到任何输出,但它会允许您访问其中的函数,因此您可以手动调用main
。如果你想了解更多关于";采购";意思是,请看我的答案:Unix:源代码和导出之间的区别是什么?。以下是一个示例运行和输出,包括在获取文件后手动调用main
函数:
eRCaGuy_hello_world/bash$ . hello_world_best.sh
eRCaGuy_hello_world/bash$ main
Running main.
重要信息:
我曾经认为使用基于"${FUNCNAME[-1]}"
的技术更好,但事实并非如此!事实证明,该技术并不能优雅地处理嵌套脚本,其中一个脚本调用或源于另一个脚本。要做到这一点,您必须1)将"${FUNCNAME[-1]}"
放入函数中,2)根据嵌套脚本的级别将索引更改为FUNCNAME
arrray,这是不实际的。因此,不要使用我以前推荐的"${FUNCNAME[-1]}"
技术。相反,使用我现在推荐的if [ "${BASH_SOURCE[0]}" = "$0" ]
技术,因为它可以完美地处理嵌套脚本!
更进一步
- 我刚刚添加了一个非常详细的示例:如何在Bash中编写、导入和使用库?,它使用了我上面的技术。我还展示了Python中相对导入的详细示例,这样它们就可以在克隆到任何人计算机上的repo中自动工作,而无需重新配置任何内容
参考文献
- 这里的主要答案是我第一次了解
"${BASH_SOURCE[0]}" = "$0"
的地方 - 我第一次了解
${FUNCNAME[-1]}
技巧的地方是:@mr.spuratic:如何检测脚本是否来源——他显然是从Dennis Williamson那里学到的 - 在过去的一年里,我们进行了大量的个人学习、尝试和努力
- 我的代码:eRCaGuy_hello_world/bash/if__name__==__main___check_if_sourced_or_executed_best.sh
- 我的另一个相关但较长的答案:如何检测脚本是否来源
我一直在所有脚本的底部使用以下构造:
[[ "$(caller)" != "0 "* ]] || main "$@"
脚本中的所有其他内容都是在函数中定义的,或者都是全局变量。
caller
被记录为"返回当前子例程调用的上下文"。当脚本被来源时,调用方的结果从来源该脚本的行号开始。如果该脚本不是源代码,则以"0 "
开始
我使用!=
和||
而不是=
和&&
的原因是后者会导致脚本在来源时返回false。如果外部脚本在set -e
下运行,这可能会导致它退出。
注意,我只知道这适用于bash。它不适用于posix外壳。我不知道其他shell,比如ksh或zsh。