似乎有两个原因会导致人们提出这个问题:要么他们想要检测用户错误并提供友好的提示,要么他们是Python程序员,希望在bash中使用Python中最独特的功能之一。
检测用户错误
有一种观点认为,在这种情况下纵容Unix用户是一个坏主意,如果Unix用户真的想要执行你的脚本而不是作为源代码使用它,你不应该提前猜测他或她的意图。现在将这个观点放在一边,我们可以重新表达问题的实质:
如果用户运行我的脚本而不是从交互式shell中作为源代码调用它,我想给出一个错误消息并中止执行。
关键在于,我重新表述问题的原因在于你实际上无法确定用户输入了什么,但你可以确定代码是否由交互式shell解释执行。你可以通过检查$-的内容中是否包含i来实现:
# POSIX
case $- in
*i*) : ;;
*) echo "You should dot me in" >&2; exit 1;;
esac
或者使用非POSIX语法:
# Bash/Ksh
if [[ $- != *i* ]]; then
echo "You should dot me in" >&2; exit 1
fi
当然,对于一些棘手的情况,比如“我希望我的文件能够从一个非交互式脚本中被点入...”。针对这些情况,请参考本节的第一段。
模拟Python的奇特行为
Python有一个奇怪的特性:有些Python脚本既是程序又是库。该脚本可以检测自身是作为程序执行还是作为库/包导入,并表现出不同的行为。例如,当作为库导入时,只定义函数,不处理命令行参数,并且不调用任何函数。
Bash也可以实现类似功能,但这并不是一种自然的方式,而且你不应该这样做。尽管如此,人们会一直纠缠我们,直到我们向他们展示如何做这种不自然的事情。
Bash有一个内部变量称为FUNCNAME,可以用于检测脚本是否被引用/包含。通常情况下,此变量只显示调用栈中的函数名称,但source或.与函数调用很相似,因此也会放入此栈中。
同时,由于这是bash,没有任何东西可以直截了当。只有当你在实际函数内部时,这个变量才存在。因此,你无法在脚本的主体部分进行此检查。你需要编写一个函数,在调用该函数之前,向下查看调用栈的一个插槽,以确定调用之前的位置。
# Bash only
libfunc1() { ...; }
libfunc2() { ...; }
sourced() {
[[ ${FUNCNAME[1]} = source ]]
}
sourced && return
# 处理命令行参数等。
注意,即使使用点操作符(.)将脚本的内容导入,名称“source”也会被放入调用栈中,因此这个检查对于这两种情况都足够有效。
如果执行该脚本,它将定义三个函数,会产生一个失败的检查结果,并继续执行“处理命令行参数等”部分,其中可能包含一些命令。如果脚本被引用/导入,它只会定义三个函数,不做其他操作。
讨论
用返回2> / dev / null也可以实现“模拟Python的奇怪行为”吗?
- 这只在bash中起作用(不适用于POSIX模式):在以外驱动程序模式运行的bash或dash等其他Shell中将无法工作,它们会在函数外或源文件中使用return时退出。POSIX规范指出:“如果Shell当前未执行函数或点脚本,则结果是未指定的。”
我认为 (( ${#BASH_SOURCE[@]} > 1 )) 更适合作为一种检测文件是否被引用的方法。
- 实际上,(( {#BASH_SOURCE[@]} -gt 1 || ( - = s ) ]] 这样的表达式更加正确。