'What is the bash equivalent to Python's `if __name__ == '__main__'`?
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?
Solution 1:[1]
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
Solution 2:[2]
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
Solution 3:[3]
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
Solution 4:[4]
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.
Solution 5:[5]
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.
Solution 6:[6]
My top 2 favorites are:
- [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 - [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
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Community |
| Solution 2 | |
| Solution 3 | Community |
| Solution 4 | |
| Solution 5 | camh |
| Solution 6 | Gabriel Staples |
