Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect if a script is being sourced

Tags:

bash

ksh

People also ask

What is a sourced script?

Sourcing a script will run the commands in the current shell process. Changes to the environment take effect in the current shell. Executing a script will run the commands in a new shell process. Changes to the environment take effect in the new shell and is lost when the script is done and the new shell is terminated.

What is $Bash_source?

BASH_SOURCE. An array variable whose members are the source filenames where the corresponding shell function names in the FUNCNAME array variable are defined.

What happens when you source a bash script?

The source Command The built-in bash source command reads and executes the content of a file. If the sourced file is a bash script, the overall effect comes down to running it. We may use this command either in a terminal or inside a bash script.

What is the source command in Linux?

source is a shell built-in command which is used to read and execute the content of a file(generally set of commands), passed as an argument in the current shell script. The command after taking the content of the specified files passes it to the TCL interpreter as a text script which then gets executed.


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

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.


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


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.


The BASH_SOURCE[] answer (bash-3.0 and later) seems simplest, though BASH_SOURCE[] is not documented to work outside a function body (it currently happens to work, in disagreement with the man page).

The most robust way, as suggested by Wirawan Purwanto, is to check FUNCNAME[1] within a function:

function mycheck() { declare -p FUNCNAME; }
mycheck

Then:

$ bash sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="main")'
$ . sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="source")'

This is the equivalent to checking the output of caller, the values main and source distinguish the caller's context. Using FUNCNAME[] saves you capturing and parsing caller output. You need to know or calculate your local call depth to be correct though. Cases like a script being sourced from within another function or script will cause the array (stack) to be deeper. (FUNCNAME is a special bash array variable, it should have contiguous indexes corresponding to the call stack, as long as it is never unset.)

function issourced() {
    [[ ${FUNCNAME[@]: -1} == "source" ]]
}

(In bash-4.2 and later you can use the simpler form ${FUNCNAME[-1]} instead for the last item in the array. Improved and simplified thanks to Dennis Williamson's comment below.)

However, your problem as stated is "I have a script where I do not want it to call 'exit' if it's being sourced". The common bash idiom for this situation is:

return 2>/dev/null || exit

If the script is being sourced then return will terminate the sourced script and return to the caller.

If the script is being executed, then return will return an error (redirected), and exit will terminate the script as normal. Both return and exit can take an exit code, if required.

Sadly, this doesn't work in ksh (at least not in the AT&T derived version I have here), it treats return as equivalent to exit if invoked outside a function or dot-sourced script.

Updated: What you can do in contemporary versions of ksh is to check the special variable .sh.level which is set to the function call depth. For an invoked script this will initially be unset, for a dot-sourced script it will be set to 1.

function issourced {
    [[ ${.sh.level} -eq 2 ]]
}

issourced && echo this script is sourced

This is not quite as robust as the bash version, you must invoke issourced() in the file you are testing from at the top level or at a known function depth.

(You may also be interested in this code on github which uses a ksh discipline function and some debug trap trickery to emulate the bash FUNCNAME array.)

The canonical answer here: http://mywiki.wooledge.org/BashFAQ/109 also offers $- as another indicator (though imperfect) of the shell state.


Notes:

  • it is possible to create bash functions named "main" and "source" (overriding the builtin), these names may appear in FUNCNAME[] but as long as only the last item in that array is tested there is no ambiguity.
  • I don't have a good answer for pdksh. The closest thing I can find applies only to pdksh, where each sourcing of a script opens a new file descriptor (starting with 10 for the original script). Almost certainly not something you want to rely on...

Editor's note: This answer's solution works robustly, but is bash-only. It can be streamlined to
(return 2>/dev/null).

TL;DR

Try to execute a return statement. If the script isn't sourced, that will raise an error. You can catch that error and proceed as you need.

Put this in a file and call it, say, 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

Execute it directly:

shell-prompt> sh test.sh
output: This script is not sourced.

Source it:

shell-prompt> source test.sh
output: This script is sourced.

For me, this works in zsh and bash.

Explanation

The return statement will raise an error if you try to execute it outside of a function or if the script is not sourced. Try this from a shell prompt:

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

You don't need to see that error message, so you can redirect the output to dev/null:

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

Now check the exit code. 0 means OK (no errors occurred), 1 means an error occurred:

shell-prompt> echo $?
output: 1

You also want to execute the return statement inside of a sub-shell. When the return statement runs it . . . well . . . returns. If you execute it in a sub-shell, it will return out of that sub-shell, rather than returning out of your script. To execute in the sub-shell, wrap it in $(...):

shell-prompt> $(return >/dev/null 2>$1)

Now, you can see the exit code of the sub-shell, which should be 1, because an error was raised inside the sub-shell:

shell-prompt> echo $?
output: 1

FWIW, after reading all of the other answers, I came up with following solution for me:

Update: Actually, somebody spotted a since-corrected error in another answer which affected mine, too. I think the update here also is an improvement (see edits if you are curious).

This works for all scripts, which start with #!/bin/bash but might be sourced by different shells as well to learn some information (like settings) which is are kept outside the main function.

According to the comments below, this answer here apparently does not work for all bash variants. Also not for systems, where /bin/sh is based on bash. I. E. it fails for bash v3.x on MacOS. (Currenty I do not know how to solve this.)

#!/bin/bash

# Function definitions (API) and shell variables (constants) go here
# (This is what might be interesting for other shells, too.)

# this main() function is only meant to be meaningful for bash
main()
{
# The script's execution part goes here
}

BASH_SOURCE=".$0" # cannot be changed in bash
test ".$0" != ".$BASH_SOURCE" || main "$@"

Instead of the last 2 lines you can use following (in my opinion less readable) code to not set BASH_SOURCE in other shells and allow set -e to work in main:

if ( BASH_SOURCE=".$0" && exec test ".$0" != ".$BASH_SOURCE" ); then :; else main "$@"; fi

This script-recipe has following properties:

  • If executed by bash the normal way, main is called. Please note that this does not include a call like bash -x script (where script does not contain a path), see below.

  • If sourced by bash, main is only called, if the calling script happens to have the same name. (For example, if it sources itself or via bash -c 'someotherscript "$@"' main-script args.. where main-script must be, what test sees as $BASH_SOURCE).

  • If sourced/executed/read/evaled by a shell other than bash, main is not called (BASH_SOURCE is always different to $0).

  • main is not called if bash reads the script from stdin, unless you set $0 to be the empty string like so: ( exec -a '' /bin/bash ) <script

  • If evaluated by bash with eval (eval "`cat script`" all quotes are important!) from within some other script, this calls main. If eval is run from commandline directly, this is similar to previous case, where the script is read from stdin. (BASH_SOURCE is blank, while $0 usually is /bin/bash if not forced to something completely different.)

  • If main is not called, it does return true ($?=0).

  • This does not rely on unexpected behavior (previously I wrote undocumented, but I found no documentation that you cannot unset nor alter BASH_SOURCE either):

    • BASH_SOURCE is a bash reserved array. But allowing BASH_SOURCE=".$0" to change it would open a very dangerous can of worms, so my expectation is, that this must have no effect (except, perhaps, some ugly warning shows up in some future version of bash).
    • There is no documentation that BASH_SOURCE works outside functions. However the opposite (that it only works in functions) is neither documented. The observation is, that it works (tested with bash v4.3 and v4.4, unfortunately I have no bash v3.x anymore) and that quite too many scripts would break, if $BASH_SOURCE stops working as observed. Hence my expectation is, that BASH_SOURCE stays as is for future versions of bash, too.
    • In contrast (nice find, BTW!) consider ( return 0 ), which gives 0 if sourced and 1 if not sourced. This comes a bit unexpected not only for me , and (according to the readings there) POSIX says, that return from subshell is undefined behavior (and the return here is clearly from a subshell). Perhaps this feature eventually gets enough widespread use such that it can no more be changed, but AFAICS there is a much higher chance that some future bash version accidental changes the return behavior in that case.
  • Unfortunately bash -x script 1 2 3 does not run main. (Compare script 1 2 3 where script has no path). Following can be used as workaround:

    • bash -x "`which script`" 1 2 3
    • bash -xc '. script' "`which script`" 1 2 3
    • That bash script 1 2 3 does not run main can be considered a feature.
  • Note that ( exec -a none script ) calls main (bash does not pass it's $0 to the script, for this you need to use -c as shown in the last point).

Thus, except for some some corner cases, main is only called, when the script is executed the usual way. Normally this is, what you want, especially because it lacks complex hard to understand code.

Note that it is very similar to the Python code:

if __name__ == '__main__': main()

Which also prevents calling of main, except for some corner cases, as you can import/load the script and enforce that __name__='__main__'

Why I think this is a good general way to solve the challenge

If you have something, which can be sourced by multiple shells, it must be compatible. However (read the other answers), as there is no (easy to implement) portable way to detect the sourceing, you should change the rules.

By enforcing that the script must be executed by /bin/bash, you exactly do this.

This solves all cases but following in which case the script cannot run directly:

  • /bin/bash is not installed or disfunctional (i. E. in a boot environment)
  • If you pipe it to a shell like in curl https://example.com/script | $SHELL
  • (Note: This is only true if your bash is recent enough. This recipe is reported to fail for certain variants. So be sure to check it works for your case.)

However I cannot think about any real reason where you need that and also the ability to source the exactly same script in parallel! Usually you can wrap it to execute the main by hand. Like that:

  • $SHELL -c '. script && main'
  • { curl https://example.com/script && echo && echo main; } | $SHELL
  • $SHELL -c 'eval "`curl https://example.com/script`" && main'
  • echo 'eval "`curl https://example.com/script`" && main' | $SHELL

Notes

  • This answer would not have been possible without the help of all the other answers! Even the wrong ones - which initially made me posting this.

  • Update: Edited due to the new discoveries found in https://stackoverflow.com/a/28776166/490291