61

I am defining a shell script which a user should source rather than execute.

Is there a conventional or intelligent way to hint to the user that this is the case, for instance via a file extension?

Is there shell code I can write in the file itself, which will cause it to echo a message and quit if it is executed instead of sourced, so that I can help the user avoid this obvious mistake?

5

6 Answers 6

66

Assuming that you are running bash, put the following code near the start of the script that you want to be sourced but not executed:

if [ "${BASH_SOURCE[0]}" -ef "$0" ]
then
    echo "Hey, you should source this script, not execute it!"
    exit 1
fi

Under bash, ${BASH_SOURCE[0]} will contain the name of the current file that the shell is reading regardless of whether it is being sourced or executed.

By contrast, $0 is the name of the current file being executed.

-ef tests if these two files are the same file. If they are, we alert the user and exit.

Neither -ef nor BASH_SOURCE are POSIX. While -ef is supported by ksh, yash, zsh and Dash, BASH_SOURCE requires bash. In zsh, however, ${BASH_SOURCE[0]} could be replaced by ${(%):-%N}.

5
  • 4
    Simply echo "Usage: source \"$myfile\""
    – kubanczyk
    Feb 16, 2018 at 7:56
  • 8
    @kubanczyk source is not portable. Considering that this answer is bash-specific, it's not that bad, but it's a good habit to use the portable .
    – gronostaj
    Feb 16, 2018 at 10:50
  • Follow-uo on @gronostaj's comment. sh (or dash) will accept the period "." for a soruce operation. As an aside, that is also the reason to use "." in profile files and not use "source".
    – will
    Jun 16, 2022 at 16:46
  • @will I am interested to know the reason to use "." in profile instead of "source". Could you please enlighten me?
    – Idonknow
    Aug 14, 2022 at 0:12
  • I wanted something that worked cross-shell; I found stackoverflow.com/a/28776166 worked great! Feb 16 at 0:29
39

A non-executable file can be sourced but not executed, so, as a first line of defense, not setting the executable flag should be a good hint...

Edit: trick I just stumbled on: make the shebang be any executable that isn't a shell interpreter, /bin/false makes the script return an error (rc!=0)

#!/bin/false "This script should be sourced in a shell, not executed directly"
5
  • 10
    However, a non-executable file can still be executed via e.g. bash somefile.sh...
    – twalberg
    Feb 16, 2018 at 16:48
  • 2
    If you know you can execute it with bash (vd perl, python, awk...), then you have looked t the source and seen the comment that says to not do it :)
    – xenoid
    Feb 16, 2018 at 21:26
  • 2
    Perl scripts are generally named somefile.pl and Python as somefile.py, so no, I probably haven't read the comments (what are those, even, anyway?) and bash somefile.sh is shorter to type than chmod +x somefile.sh; ./somefile.sh...
    – twalberg
    Feb 16, 2018 at 21:49
  • Also some Bourne-like shells, including bash, will first try to execve a file, but if that fails, they inspect the file manually and manually interpret the #! and invoke it through that interpreter: this is a legacy from the days when the #! was a purely userspace convention, instead of being handled by the kernel itself. I think bash, at least, will not do this for non-executable files, but I don't know if it's portable to expect such sane behavior from all shells that the user might be invoking the script from.
    – mtraceur
    Feb 16, 2018 at 23:11
  • "If you know you can execute it with bash" Um, no, sometimes user just has no idea that there exist other shells than bash and that bash script.sh can be hazardous. Feb 17, 2018 at 19:22
12

There are several methods suggested in this Stack Overflow post, of which, I liked the function-based one suggested by Wirawan Purwanto and mr.spuratic best:

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

So you can add to the start of the script:

function check()
{
    if [[ ${FUNCNAME[-1]} != "source" ]]   # bash 4.2+, use ${FUNCNAME[@]: -1} for older
    then
        printf "Usage: source %s\n" "$0"
        exit 1
    fi
}
check
8

When you source a shell script, the shebang line is ignored. By putting in an invalid shebang, you can alert the user that the script was erroneously executed:

#!/bin/bash source-this-script
# ...

The error message will be this:

/bin/bash: source-this-script: No such file or directory

The (arbitrary) argument name already provides a strong hint, but the error message still isn't 100% clear. We can fix this with a utility script source-this-script that is placed somewhere in your PATH:

#!/bin/sh
echo >&2 "This script must be sourced, not executed${1:+: }${1:-!}"
exit 1

Now, the error message will be this:

This script must be sourced, not executed: path/to/script.sh

Comparison to other approaches

Compared to the other answers, this approach only requires minimal changes to each script (and having a shebang line helps with file type detection in editors and specifies the shell script dialect, so there are even benefits). The downside is a somewhat unclear error message, or the (one-time) addition of another shell script.

It does not prevent explicit invocation via bash path/to/script.sh, though (thanks @muru!).

3
  • 2
    Another downside is that this won't protect against bash some/script.sh, which would also ignore the shebang.
    – muru
    Feb 16, 2018 at 9:18
  • 4
    You can make the message more clear by making the shebang #!/bin/echo 'You must source this script!' or something like that.
    – Chris
    Feb 16, 2018 at 9:19
  • 1
    @Chris: Right, but then I would lose the file type detection (e.g. in Vim) and documentation of which shell dialect this is. If you don't care about these, your suggestion indeed would get rid of the second script! Feb 16, 2018 at 9:25
7

Assuming it is just useless, rather than harmful, to execute the script, you can add

return 0 || printf 'Must be sourced, not executed\n' >&2

to the end of the script. return outside of a function has a non-zero exit code unless the file is being sourced.

1
  • 5
    Note that this returns a 0 exit status. Try this similar idiom that I use instead: return 2>/dev/null; echo "$0: This script must be sourced" 1>&2; exit 1
    – wjandrea
    Feb 16, 2018 at 19:52
0

Here are 4 techniques I've compiled. You can programmatically decide what to do if the script is being executed versus sourced.

From check_if_sourced_or_executed.sh in my eRCaGuy_hello_world repo:

#!/usr/bin/env bash

main() {
    echo "Running main function."

    # put your code here

    echo -e "\nTOTAL TIME TO RUN main func:"
}

# Set the global variable `run` to "true" if the script is being executed and `main` should run, and
# set `run` to "false" otherwise--ex: if the script is being sourced in order to import functions
# from the script, but `main` should NOT run.
run_check() {
    # DEMONSTRATE A FEW DIFFERENT TECHNIQUES

    echo "Technique 1 (can be placed anywhere):"
    echo "  See: https://unix.stackexchange.com/a/424495/114401"
    if [ "${BASH_SOURCE[0]}" -ef "$0" ]; then
        echo "  This script is being EXECUTED."
        run="true"
    else
        echo "  This script is being SOURCED."
    fi


    echo "Technique 2 [MY 2ND FAVORITE] (can be placed anywhere):"
    echo "  Modified from: https://stackoverflow.com/a/29967433/4561887"
    if [ "${BASH_SOURCE[0]}" == "$0" ]; then
        echo "  This script is being EXECUTED."
        run="true"
    else
        echo "  This script is being SOURCED."
    fi

    echo "Technique 3 (requires another line which MUST be **outside** all functions):"
    echo "  Modified from: https://stackoverflow.com/a/28776166/4561887"
    if [ "$script_is_being_executed" == "true" ]; then
        echo "  This script is being EXECUTED."
        run="true"
    else
        echo "  This script is being SOURCED."
    fi


    echo "Technique 4 [MY 1ST FAVORITE] (MUST be **inside** a function):"
    echo "  Modified from: https://stackoverflow.com/a/14706745/4561887"
    echo "  and https://unix.stackexchange.com/a/424552/114401"
    if [ "${FUNCNAME[-1]}" == "main" ]; then
        echo "  This script is being EXECUTED."
        run="true"
    elif [ "${FUNCNAME[-1]}" == "source" ]; then
        echo "  This script is being SOURCED."
    else
        echo "  ERROR: THIS TECHNIQUE IS BROKEN"
    fi

}

# ==================================================================================================
# Main program entry point
# ==================================================================================================

# Only run main function if this file is being executed, NOT sourced.

# For Technique 3. This `return` statement MUST be called at a top-level scope, outside of all
# functions, in order to work! See their detailed explanation here for how it works:
# https://stackoverflow.com/a/28776166/4561887
(return 0 2>/dev/null) && script_is_being_executed="false" || script_is_being_executed="true"

run="false"
run_check
if [ "$run" == "true" ]; then
    time main "$@"
fi

Test run commands:

# 1. Execute the script in 2 ways:
#   A. As an executable script
./bash/check_if_sourced_or_executed.sh
#   B. By calling the bash interpreter
bash ./bash/check_if_sourced_or_executed.sh

# 2. Source the script in 2 ways:
#   A. Bash source call
source ./bash/check_if_sourced_or_executed.sh
#   B. (Recommended) POSIX-compliant source call
. ./bash/check_if_sourced_or_executed.sh

See also where I described the 4 techniques on Stack Overflow here: https://stackoverflow.com/questions/2683279/how-to-detect-if-a-script-is-being-sourced/70662049#70662049

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .