How to detect if a script is being sourced

Solution 1:

If your Bash version knows about the BASH_SOURCE array variable, try something like:

# man bash | less -p BASH_SOURCE
#[[ ${BASH_VERSINFO[0]} -le 2 ]] && echo 'No BASH_SOURCE array variable' && exit 1

[[ "${BASH_SOURCE[0]}" != "${0}" ]] && echo "script ${BASH_SOURCE[0]} is being sourced ..."

Solution 2:

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.

One-liners follow - explanation below; the cross-shell version is complex, but it should work robustly:

  • bash (verified on 3.57 and 4.4.19)

    (return 0 2>/dev/null) && sourced=1 || sourced=0
    
  • ksh (verified on 93u+)

    [[ $(cd "$(dirname -- "$0")" && 
       printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] &&
         sourced=1 || sourced=0
    
  • zsh (verified on 5.0.5) - be sure to call this outside of a function

    [[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0
    
  • cross-shell (bash, ksh, zsh)

    ([[ -n $ZSH_EVAL_CONTEXT && $ZSH_EVAL_CONTEXT =~ :file$ ]] || 
     [[ -n $KSH_VERSION && $(cd "$(dirname -- "$0")" &&
        printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.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_EVAL_CONTEXT" ]; 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|dash) sourced=1;; esac
    fi
    

Explanation:


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")" && printf '%s' "${PWD%/}/")$(basename -- "$0") != \
   "${.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 exactly identical in the non-sourced case, as $0 may be a relative path, while ${.sh.file} is always a full path, so $0 must be resolved to a full path before comparing.


zsh

[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0

$ZSH_EVAL_CONTEXT contains information about the evaluation context - call this outside of a function. Inside a sourced script['s top-level scope], $ZSH_EVAL_CONTEXT ends with :file.

Caveat: Inside a command substitution, zsh appends :cmdsubst, so test $ZSH_EVAL_CONTEXT for :file:cmdsubst$ there.


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 fails if your script is being sourced by another script.

The section "How to handle sourced invocations" in this answer of mine discusses the edge cases that cannot be handled with POSIX features only in detail.

This 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 with a fallback solution for all remaining shells.

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_EVAL_CONTEXT" ]; 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|dash) sourced=1;; esac
fi

[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.

Solution 3:

This seems to be portable between Bash and Korn:

[[ $_ != $0 ]] && echo "Script is being sourced" || echo "Script is a subshell"

A line similar to this or an assignment like pathname="$_" (with a later test and action) must be on the first line of the script or on the line after the shebang (which, if used, should be for ksh in order for it to work under the most circumstances).

Solution 4:

After reading @DennisWilliamson's answer, there are some issues, see below:

As this question stand for ksh and bash, there is another part in this answer concerning ksh... see below.

Simple bash way

[ "$0" = "$BASH_SOURCE" ]

Let's try (on the fly because that bash could ;-):

source <(echo $'#!/bin/bash
           [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
           echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 29301 is sourced (bash, /dev/fd/63)

bash <(echo $'#!/bin/bash
           [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
           echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 16229 is own (/dev/fd/63, /dev/fd/63)

I use source instead off . for readability (as . is an alias to source):

. <(echo $'#!/bin/bash
           [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
           echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 29301 is sourced (bash, /dev/fd/63)

Note that process number don't change while process stay sourced:

echo $$
29301

Why not to use $_ == $0 comparison

For ensuring many case, I begin to write a true script:

#!/bin/bash

# As $_ could be used only once, uncomment one of two following lines

#printf '_="%s", 0="%s" and BASH_SOURCE="%s"\n' "$_" "$0" "$BASH_SOURCE"
[[ "$_" != "$0" ]] && DW_PURPOSE=sourced || DW_PURPOSE=subshell

[ "$0" = "$BASH_SOURCE" ] && BASH_KIND_ENV=own || BASH_KIND_ENV=sourced;
echo "proc: $$[ppid:$PPID] is $BASH_KIND_ENV (DW purpose: $DW_PURPOSE)"

Copy this to a file called testscript:

cat >testscript   
chmod +x testscript

Now we could test:

./testscript 
proc: 25758[ppid:24890] is own (DW purpose: subshell)

That's ok.

. ./testscript 
proc: 24890[ppid:24885] is sourced (DW purpose: sourced)

source ./testscript 
proc: 24890[ppid:24885] is sourced (DW purpose: sourced)

That's ok.

But,for testing a script before adding -x flag:

bash ./testscript 
proc: 25776[ppid:24890] is own (DW purpose: sourced)

Or to use pre-defined variables:

env PATH=/tmp/bintemp:$PATH ./testscript 
proc: 25948[ppid:24890] is own (DW purpose: sourced)

env SOMETHING=PREDEFINED ./testscript 
proc: 25972[ppid:24890] is own (DW purpose: sourced)

This won't work anymore.

Moving comment from 5th line to 6th would give more readable answer:

./testscript 
_="./testscript", 0="./testscript" and BASH_SOURCE="./testscript"
proc: 26256[ppid:24890] is own

. testscript 
_="_filedir", 0="bash" and BASH_SOURCE="testscript"
proc: 24890[ppid:24885] is sourced

source testscript 
_="_filedir", 0="bash" and BASH_SOURCE="testscript"
proc: 24890[ppid:24885] is sourced

bash testscript 
_="/bin/bash", 0="testscript" and BASH_SOURCE="testscript"
proc: 26317[ppid:24890] is own

env FILE=/dev/null ./testscript 
_="/usr/bin/env", 0="./testscript" and BASH_SOURCE="./testscript"
proc: 26336[ppid:24890] is own

Harder: ksh now...

As I don't use ksh a lot, after some read on the man page, there is my tries:

#!/bin/ksh

set >/tmp/ksh-$$.log

Copy this in a testfile.ksh:

cat >testfile.ksh
chmod +x testfile.ksh

Than run it two time:

./testfile.ksh
. ./testfile.ksh

ls -l /tmp/ksh-*.log
-rw-r--r-- 1 user user   2183 avr 11 13:48 /tmp/ksh-9725.log
-rw-r--r-- 1 user user   2140 avr 11 13:48 /tmp/ksh-9781.log

echo $$
9725

and see:

diff /tmp/ksh-{9725,9781}.log | grep ^\> # OWN SUBSHELL:
> HISTCMD=0
> PPID=9725
> RANDOM=1626
> SECONDS=0.001
>   lineno=0
> SHLVL=3

diff /tmp/ksh-{9725,9781}.log | grep ^\< # SOURCED:
< COLUMNS=152
< HISTCMD=117
< LINES=47
< PPID=9163
< PS1='$ '
< RANDOM=29667
< SECONDS=23.652
<   level=1
<   lineno=1
< SHLVL=2

There is some variable herited in a sourced run, but nothing really related...

You could even check that $SECONDS is close to 0.000, but that's ensure only manualy sourced cases...

You even could try to check for what's parent is:

Place this into your testfile.ksh:

ps $PPID

Than:

./testfile.ksh
  PID TTY      STAT   TIME COMMAND
32320 pts/4    Ss     0:00 -ksh

. ./testfile.ksh
  PID TTY      STAT   TIME COMMAND
32319 ?        S      0:00 sshd: user@pts/4

or ps ho cmd $PPID, but this work only for one level of subsessions...

Sorry, I couldn't find a reliable way of doing that, under ksh.