Robust solutions for bash
, ksh
, zsh
, including a cross-shell one, plus a reasonably robust POSIX-compliant solution:
Version numbers given are the ones on which functionality was verified - likely, these solutions work on much earlier versions, too - feedback welcome.
Using POSIX features only (such as in dash
, which acts as /bin/sh
on Ubuntu), there is no robust way to determine if a script is being sourced - see below for the best approximation.
Important:
The solutions determine whether the script is being sourced by its caller, which may be a shell itself or another script (which may or may not be sourced itself):
Also detecting the latter case adds complexity; if you do not need to detect the case when your script is being sourced by another script, you can use the following, relatively simple POSIX-compliant solution:
# Helper function
is_sourced() {
if [ -n "$ZSH_VERSION" ]; then
case $ZSH_EVAL_CONTEXT in *:file:*) return 0;; esac
else # Add additional POSIX-compatible shell names here, if needed.
case ${0##*/} in dash|-dash|bash|-bash|ksh|-ksh|sh|-sh) return 0;; esac
fi
return 1 # NOT sourced.
}
# Sample call.
is_sourced && sourced=1 || sourced=0
All solutions below must run in the top-level scope of your script, not inside functions.
One-liners follow - explanation below; the cross-shell version is complex, but it should work robustly:
- bash (verified on 3.57, 4.4.19, and 5.1.16)
(return 0 2>/dev/null) && sourced=1 || sourced=0
[[ "$(cd -- "$(dirname -- "$0")" && pwd -P)/$(basename -- "$0")" != "$(cd -- "$(dirname -- "${.sh.file}")" && pwd -P)/$(basename -- "${.sh.file}")" ]] && sourced=1 || sourced=0
[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0
- cross-shell (bash, ksh, zsh)
(
[[ -n $ZSH_VERSION && $ZSH_EVAL_CONTEXT =~ :file$ ]] ||
[[ -n $KSH_VERSION && "$(cd -- "$(dirname -- "$0")" && pwd -P)/$(basename -- "$0")" != "$(cd -- "$(dirname -- "${.sh.file}")" && pwd -P)/$(basename -- "${.sh.file}")" ]] ||
[[ -n $BASH_VERSION ]] && (return 0 2>/dev/null)
) && sourced=1 || sourced=0
- POSIX-compliant; not a one-liner (single pipeline) for technical reasons and not fully robust (see bottom):
sourced=0
if [ -n "$ZSH_VERSION" ]; then
case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
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
(return 0 2>/dev/null) && sourced=1
else # All other shells: examine $0 for known shell binary filenames.
# Detects `sh` and `dash`; add additional shell filenames as needed.
case ${0##*/} in sh|-sh|dash|-dash) sourced=1;; esac
fi
Explanations
bash
(return 0 2>/dev/null) && sourced=1 || sourced=0
Note: The technique was adapted from user5754163's answer, as it turned out to be more robust than the original solution, [[ $0 != "$BASH_SOURCE" ]] && sourced=1 || sourced=0
[1]
Bash allows return
statements only from functions and, in a script's top-level scope, only if the script is sourced.
- If
return
is used in the top-level scope of a non-sourced script, an error message is emitted, and the exit code is set to 1
.
(return 0 2>/dev/null)
executes return
in a subshell and suppresses the error message; afterwards the exit code indicates whether the script was sourced (0
) or not (1
), which is used with the &&
and ||
operators to set the sourced
variable accordingly.
- Use of a subshell is necessary, because executing
return
in the top-level scope of a sourced script would exit the script.
- Tip of the hat to @Haozhun, who made the command more robust by explicitly using
0
as the return
operand; he notes: per bash help of return [N]
: "If N is omitted, the return status is that of the last command." As a result, the earlier version [which used just return
, without an operand]
produces incorrect result if the last command on the user's shell has a non-zero return value.
ksh
[[ "$(cd -- "$(dirname -- "$0")" && pwd -P)/$(basename -- "$0")" != "$(cd -- "$(dirname -- "${.sh.file}")" && pwd -P)/$(basename -- "${.sh.file}")" ]] && sourced=1 || sourced=0
Special variable ${.sh.file}
is somewhat analogous to $BASH_SOURCE
; note that ${.sh.file}
causes a syntax error in bash, zsh, and dash, so be sure to execute it conditionally in multi-shell scripts.
Unlike in bash, $0
and ${.sh.file}
are NOT guaranteed to be the same - at different times either may be a relative path or mere file name, while the other may be a full one; therefore, both $0
and ${.sh.file}
must be resolved to full paths before comparing. If the full paths differ, sourcing is implied.
zsh
[[ $ZSH_EVAL_CONTEXT =~ :file$) ]] && sourced=1 || sourced=0
$ZSH_EVAL_CONTEXT
contains information about the evaluation context: substring file
, separated with :
, is only present if the script is being sourced.
In a sourced script's top-level scope, $ZSH_EVAL_CONTEXT
ends with :file
, and that's what this test is limited to. Inside a function, :shfunc
is appended to :file
; inside a command substitution, :cmdsubst
, is appended.
Using POSIX features only
If you're willing to make certain assumptions, you can make a reasonable, but not fool-proof guess as to whether your script is being sourced, based on knowing the binary filenames of the shells that may be executing your script.
Notably, this means that this approach doesn't detect the case when your script is being sourced by another script.
The section "How to handle sourced invocations" in this answer discusses the edge cases that cannot be handled with POSIX features only in detail.
Examining the binary filename relies on the standard behavior of $0
, which zsh
, for instance, does not exhibit.
Thus, the safest approach is to combine the robust, shell-specific methods above - which do not rely on $0
- with a $0
-based fallback solution for all remaining shells.
In short: The following solution:
in the shells that are covered with shell-specific tests: works robustly.
in all other shells: works only as expected when the script is being sourced directly from such a shell, as opposed to from another script.
Tip of the hat to Stéphane Desneux and his answer for the inspiration (transforming my cross-shell statement expression into a sh
-compatible if
statement and adding a handler for other shells).
sourced=0
if [ -n "$ZSH_VERSION" ]; then
case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
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
(return 0 2>/dev/null) && sourced=1
else # All other shells: examine $0 for known shell binary filenames.
# Detects `sh` and `dash`; add additional shell filenames as needed.
case ${0##*/} in sh|-sh|dash|-dash) sourced=1;; esac
fi
Note that, for robustness, each shell binary filename (e.g. sh
) is represented twice - once as-is and a second time, prefixed with -
. This is to account for environments, such as macOS, where interactive shells are launched as login shells with a custom $0
value that is the (path-less) shell filename prefixed with -
.Thanks, t7e.
(While sh
and dash
are perhaps unlikely to be used as interactive shells, others that you may need to add to the list may.)
[1] user1902689 discovered that [[ $0 != "$BASH_SOURCE" ]]
yields a false positive when you execute a script located in the $PATH
by passing its mere filename to the bash
binary; e.g., bash my-script
, because $0
is then just my-script
, whereas $BASH_SOURCE
is the full path. While you normally wouldn't use this technique to invoke scripts in the $PATH
- you'd just invoke them directly (my-script
) - it is helpful when combined with -x
for debugging.