What is the bash equivalent to Python's `if __name__ == '__main__'`?
Asked Answered
S

6

51

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?
Schall answered 30/4, 2015 at 11:24 Comment(4)
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?Boomerang
@Boomerang 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 )Schall
possible duplicate of How to detect if a script is being sourcedGaylord
See also: Importing functions from a shell script. I've added a complete Bash library creation and importing example there for anyone interested.Drinker
S
40

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    
Schall answered 30/4, 2015 at 12:10 Comment(5)
$0 itself is not entirely reliable so I'm not sure this is necessarily going to work either.Woodman
@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 and here )Schall
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).Woodman
${BASH_SOURCE[0]} plays nicer with igncr than ${0} does:Elope
Upvoted. After about a year of study and usage, I finally agree that comparing ${BASH_SOURCE[0]} against $0 really is the best technique, as this answer shows, since this technique properly handles nested scripts, where one script calls or sources another. If you are like me, though, and want it to look even more Pythonic, check out my new answer here which I think beautifies and self-documents it even more.Drinker
T
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
Tunisia answered 31/8, 2017 at 18:44 Comment(0)
D
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

Danicadanice answered 30/4, 2015 at 11:26 Comment(0)
T
9

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.

Tunisia answered 1/9, 2017 at 16:32 Comment(0)
D
5

I really think this is the most Pythonic and beautiful way to accomplish the equivalent of if __name__ == "__main__" in Bash:

From hello_world_best.sh in my eRCaGuy_hello_world repo:

#!/usr/bin/env bash

main() {
    echo "Running main."
    # Add your main function code here
}

if [ "${BASH_SOURCE[0]}" = "$0" ]; then
    # This script is being run.
    __name__="__main__"
else
    # This script is being sourced.
    __name__="__source__"
fi

# Only run `main` if this script is being **run**, NOT sourced (imported)
if [ "$__name__" = "__main__" ]; then
    main "$@"
fi

To run it, run ./hello_world_best.sh. Here is a sample run and output:

eRCaGuy_hello_world/bash$ ./hello_world_best.sh 
Running main.

To source (import) it, run . ./hello_world_best.sh, . hello_world_best.sh, or source hello_world_best.sh, etc. When you source it, you'll see no output in this case, but it will give you access to the functions therein, so you can manually call main for instance. If you want to learn more about what "sourcing" means, see my answer here: Unix: What is the difference between source and export?. Here is a sample run and output, including manually calling the main function after sourcing the file:

eRCaGuy_hello_world/bash$ . hello_world_best.sh 
eRCaGuy_hello_world/bash$ main
Running main.

Important:

I used to believe that using a technique based on "${FUNCNAME[-1]}" was better, but that is not the case! It turns out that technique does not gracefully handle nested scripts, where one script calls or sources another. To make that work, you have to 1) put "${FUNCNAME[-1]}" inside a function, and 2) change the index into the FUNCNAME arrray based on the level of nested scripts, which isn't practical. So, do not use the "${FUNCNAME[-1]}" technique I used to recommend. Instead, use the if [ "${BASH_SOURCE[0]}" = "$0" ] technique I now recommend, as it perfectly handles nested scripts!

Going further

  1. I just added a really Detailed example: how do you write, import, and use libraries in Bash?, which uses my technique above. I also show detailed examples of relative imports in Python, so that they will automatically work within a repo cloned onto anyone's computer without reconfiguring anything.

References

  1. The main answer here where I first learned about "${BASH_SOURCE[0]}" = "$0"
  2. Where I first learned about the ${FUNCNAME[-1]} trick: @mr.spuratic: How to detect if a script is being sourced - he learned it from Dennis Williamson apparently.
  3. A lot of personal study, trial, and effort over the last year.
  4. My code: eRCaGuy_hello_world/bash/if__name__==__main___check_if_sourced_or_executed_best.sh
  5. My other related but longer answer: How to detect if a script is being sourced
Drinker answered 11/1, 2022 at 5:41 Comment(0)
T
4

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.

Teakwood answered 25/6, 2015 at 10:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.