45

In Bash, I would like to be able to both source a script and execute the file. What is Bash's equivalent to Python's if __name__ == '__main__'?

I didn't find a readily available question/solution about this topic on Stackoverflow (I suspect I am asking in such a way that doesn't match an existing question/answer, but this is the most obvious way I can think to phrase the question because of my Python experience).


p.s. regarding the possible duplicate question (if I had more time, I would have written a shorter response):

The linked to question asks "How to detect if a script is being sourced" but this question asks "how do you create a bash script that can be both sourced AND run as a script?". The answer to this question may use some aspects of the previous question but has additional requirements/questions as follows:

  • Once you detect the script is being sourced what is the best way to not run the script (and avoid unintended side-effects (other than importing the functions of interest) like adding/removing/modifying the environment/variables)
  • Once you detect the script is being run instead of sourced what is the canonical way of implementing your script (put it in a function? or maybe just put it after the if statement? if you put it after the if statement will it have side-affects?
  • most google searches I found on Bash do not cover this topic (a bash script that can be both sourced and executed) what is the canonical way to implement this? is the topic not covered because it is discouraged or bad to do? are there gotchas?
user2357112
  • 235,058
  • 25
  • 372
  • 444
Trevor Boyd Smith
  • 16,711
  • 29
  • 116
  • 163
  • Not being fluent in Python, I don't understand your question. :) Is the idea that you want to be able to detect whether a script was run explicitly, or through inclusion in another script using `source` or the dot operator? – ghoti Apr 30 '15 at 11:29
  • @ghoti For example I have a script `script1.sh` with a function `xyz()` that I would like to use in another script `script2.sh`. When I source script1 from script2, script1 somehow does an exit that prevents me from ever calling `xyz()` in script2. (a good explanation of the python bit I refer to is provided [here](http://stackoverflow.com/questions/419163/what-does-if-name-main-do) ) – Trevor Boyd Smith Apr 30 '15 at 11:44
  • possible duplicate of [How to detect if a script is being sourced](http://stackoverflow.com/questions/2683279/how-to-detect-if-a-script-is-being-sourced) – Andrea Corbellini Apr 30 '15 at 12:39

6 Answers6

38

Solution:

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi

I added this answer because I wanted an answer that was written in a style to mimic Python's if __name__ == '__main__' but in Bash.

Regarding the usage of BASH_SOURCE vs $_. I use BASH_SOURCE because it appears to be more robust than $_ (link1, link2).


Here is an example that I tested/verified with two Bash scripts.

script1.sh with xyz() function:

#!/bin/bash

xyz() {
    echo "Entering script1's xyz()"
}

main() {
    xyz
    echo "Entering script1's main()"
}

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi

script2.sh that tries to call function xyz():

#!/bin/bash

source script1.sh

xyz    
Community
  • 1
  • 1
Trevor Boyd Smith
  • 16,711
  • 29
  • 116
  • 163
  • `$0` itself is [not entirely reliable](http://mywiki.wooledge.org/BashFAQ/028#Why_.240_is_NOT_an_option) so I'm not sure this is necessarily going to work either. – Etan Reisner Apr 30 '15 at 13:01
  • @EtanReisner If you think you have a better solution then feel free to write your own answer. (At this point in time there are 60+ upvotes for answers using `$0` to solve two different problems ([here](http://stackoverflow.com/questions/2683279/how-to-detect-if-a-script-is-being-sourced) and [here](http://stackoverflow.com/questions/29966449/what-is-the-bash-equivalent-to-pythons-if-name-main) ) – Trevor Boyd Smith Apr 30 '15 at 15:06
  • I don't. I just wanted to point out that `$0` is known to be unreliable and that that may cause problems here (but likely shouldn't in most cases). – Etan Reisner Apr 30 '15 at 15:15
  • `${BASH_SOURCE[0]}` plays nicer with [igncr](https://stackoverflow.com/a/30181124/7255) than `${0}` does: – Steve Pitchers Mar 08 '19 at 15:07
10

Use FUNCNAME

FUNCNAME is an array that's only available within a function, where FUNCNAME[0] is the name of the function, and FUNCNAME[1] is the name of the caller. So for a top-level function, FUNCNAME[1] will be main in an executed script, or source in a sourced script.

#!/bin/bash

get_funcname_1(){
    printf '%s\n' "${FUNCNAME[1]}"
}

_main(){
    echo hello
}

if [[ $(get_funcname_1) == main ]]; then
    _main
fi

Example run:

$ bash funcname_test.sh 
hello
$ source funcname_test.sh 
$ _main
hello

I'm not sure how I stumbled across this. man bash doesn't mention the source value.

Alternate method: Use FUNCNAME[0] outside a function

This only works in 4.3 and 4.4 and is not documented.

if [[ ${FUNCNAME[0]} == main ]]; then
    _main
fi
wjandrea
  • 23,210
  • 7
  • 49
  • 68
9

There is none. I usually use this:

#!/bin/bash

main()
{
    # validate parameters

    echo "In main: $@"

    # your code here
}

main "$@"

If you'd like to know whether this script is being source'd, just wrap your main call in

if [[ "$_" != "$0" ]]; then
    echo "Script is being sourced, not calling main()" 
else
    echo "Script is a subshell, calling main()"
    main "$@"
fi

Reference: How to detect if a script is being sourced

Community
  • 1
  • 1
dekkard
  • 4,015
  • 1
  • 14
  • 25
7

return 2> /dev/null

For an idiomatic Bash way to do this, you can use return like so:

_main(){
    echo hello
}

# End sourced section
return 2> /dev/null

_main

Example run:

$ bash return_test.sh 
hello
$ source return_test.sh 
$ _main
hello

If the script is sourced, return will return to the parent (of course), but if the script is executed, return will produce an error which gets hidden, and the script will continue execution.

I have tested this on GNU Bash 4.2 to 5.0, and it's my preferred solution.

Warning: This doesn't work in most other shells.

This is based on part of mr.spuratic's answer on How to detect if a script is being sourced.

wjandrea
  • 23,210
  • 7
  • 49
  • 68
3

I have been using the following construct at the bottom of all my scripts:

[[ "$(caller)" != "0 "* ]] || main "$@"

Everything else in the script is defined in a function or is a global variable.

caller is documented to "Return the context of the current subroutine call." When a script is sourced, the result of caller starts with the line number of the script sourcing this one. If this script is not being sourced, it starts with "0 "

The reason I use != and || instead of = and && is that the latter will cause the script to return false when sourced. This may cause your outer script to exit if it is running under set -e.

Note that I only know this works with bash. It wont work with a posix shell. I don't know about other shells such as ksh or zsh.

camh
  • 38,637
  • 12
  • 58
  • 67
0

My top 2 favorites are:

  1. [My 1st Favorite] (MUST be inside a function): Modified from: How to detect if a script is being sourced
    and https://unix.stackexchange.com/questions/424492/how-to-define-a-shell-script-to-be-sourced-not-run/424552#424552
    and also mentioned in a slightly-different form here: What is the bash equivalent to Python's `if __name__ == '__main__'`?
    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
    
  2. [My 2nd Favorite] (can be placed anywhere): Modified from: What is the bash equivalent to Python's `if __name__ == '__main__'`?
    if [ "${BASH_SOURCE[0]}" == "$0" ]; then
        echo "  This script is being EXECUTED."
        run="true"
    else
        echo "  This script is being SOURCED."
    fi
    

See all 4 techniques I compiled in my other answer here: How to detect if a script is being sourced

Gabriel Staples
  • 22,024
  • 5
  • 133
  • 166