我有一个脚本,我不希望它调用退出,如果它是来源。

我想检查是否$0 == bash,但这有问题,如果脚本是从另一个脚本,或者如果用户从不同的shell,如ksh源。

是否有一种可靠的方法来检测脚本是否被引用?


当前回答

$_是很脆弱的。你必须检查它作为你在脚本中做的第一件事。即使这样,它也不保证包含shell的名称(如果是源的)或脚本的名称(如果是执行的)。

例如,如果用户设置了BASH_ENV,那么在脚本的顶部,$_包含BASH_ENV脚本中执行的最后一个命令的名称。

我发现最好的方法是像这样使用0美元:

name="myscript.sh"

main()
{
    echo "Script was executed, running main..."
}

case "$0" in *$name)
    main "$@"
    ;;
esac

不幸的是,这种方式在zsh中并不能开箱使用,因为functionargzero选项的功能超出了它的名称,并且在默认情况下是打开的。

为了解决这个问题,我把unsetopt functionarg0放在我的.zshenv中。

其他回答

我最终检查[[$_ == "$(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运行实际的脚本文件,所以我使用-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

我认为在ksh和bash中没有任何可移植的方法来做到这一点。在bash中,您可以使用调用器输出来检测它,但我认为ksh中不存在等效的输出。

解决这个问题的方法不是编写需要知道这些事情才能正确运行的代码。做到这一点的方法是将代码放入函数中,而不是放入需要源代码的脚本主线中。

函数内部的代码可以只返回0或1。这只终止了函数,因此控制返回到调用该函数的任何对象。

无论从源脚本的主线调用函数,还是从顶级脚本的主线调用函数,或者从另一个函数调用函数,都是如此。

使用sourcing来引入“库”脚本,这些脚本只定义函数和变量,但实际上不执行任何其他顶级命令:

. 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

编者注:这个答案的解决方案工作稳健,但只有bash。它可以简化为 (返回2 > / dev / null)。

博士TL;

尝试执行return语句。如果脚本没有来源,则会引发错误。您可以捕获该错误并按照需要进行操作。

把它放在一个文件中,并调用它,比如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中工作。

解释

如果您试图在函数之外执行return语句,或者如果脚本不是源代码,则return语句将引发错误。在shell提示符中尝试以下操作:

shell-prompt> return
output: ...can only `return` from a function or sourced script

你不需要看到错误消息,所以你可以将输出重定向到dev/null:

shell-prompt> return >/dev/null 2>&1

现在检查逃生码。0表示OK(没有发生错误),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的答案。Korn shell,对不起。假设脚本名为include2.sh;然后在include2.sh中创建一个名为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的自省功能,即内置变量FUNCNAME和BASH_SOURCE;请参阅bash手册页中的文档。

只有两个警告:

1)对am_I_called的调用必须发生在源脚本中,而不是在任何函数中,以免${FUNCNAME[1]}返回其他东西。是的…您本可以检查${FUNCNAME[2]},但这样做只会使您的工作更加困难。

2)函数am_I_called必须驻留在源脚本中,如果你想知道被包含的文件的名称。