36

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?

  • 1
    So, if the user is writing a one-line shell script x, which just contains the command . your-script-to-be-sourced, it's OK, but if he wants to execute bash your-script-to-be-sourced it should be forbidden? What's the point of this restriction? – user1934428 Feb 16 '18 at 5:41
  • 8
    @user1934428 Of course. This is normal for a script that calculates a number of env variables and leaves these as de facto script's output. A novice will get stuck for days with the puzzle if you allow them to execute. – kubanczyk Feb 16 '18 at 7:53
  • 2
44

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

  • 2
    Simply echo "Usage: source \"$myfile\"" – kubanczyk Feb 16 '18 at 7:56
  • 6
    @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 '18 at 10:50
32

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

  • 6
    However, a non-executable file can still be executed via e.g. bash somefile.sh... – twalberg Feb 16 '18 at 16:48
  • 1
    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 '18 at 21:26
  • 1
    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 '18 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 '18 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. – Sergiy Kolodyazhnyy Feb 17 '18 at 19:22
9

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

  • 3
    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 '18 at 19:52
4

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

  • Another downside is that this won't protect against bash some/script.sh, which would also ignore the shebang. – muru Feb 16 '18 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 '18 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! – Ingo Karkat Feb 16 '18 at 9:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.