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:
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
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
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
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.
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.
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!
References
The main answer here where I first learned about "${BASH_SOURCE[0]}" = "$0"
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.
A lot of personal study, trial, and effort over the last year.
My code: eRCaGuy_hello_world/bash/if__name__==__main___check_if_sourced_or_executed_best.sh
My other related but longer answer: How to detect if a script is being sourced
Related
While working on a project written in bash by my former colleague, I noticed that all .sh files contain nothing but function definitions start with #!/bin/false, which is, as I understand, a safety mechanism of preventing execution of include-only files.
Example:
my_foo.sh
#!/bin/false
function foo(){
echo foontastic
}
my_script.sh
#!/bin/bash
./my_foo.sh # does nothing
foo # error, no command named "foo"
. ./my_foo.sh
foo # prints "foontastic"
However when I don't use #!/bin/false, effects of both proper and improper use are exactly the same:
Example:
my_bar.sh
function bar(){
echo barvelous
}
my_script.sh
#!/bin/bash
./my_bar.sh # spawn a subshell, defines bar and exit, effectively doing nothing
bar # error, no command named "bar"
. ./my_bar.sh
bar # prints "barvelous"
Since properly using those scripts by including them with source in both cases works as expected, and executing them in both cases does nothing from the perspective of a parent shell and generate no error message concerning invalid use, what is exactly the purpose of #!/bash/false in those script?
In general, let’s consider a file testcode with bash code in it
#!/bin/bash
if [ "$0" = "${BASH_SOURCE[0]}" ]; then
echo "You are executing ${BASH_SOURCE[0]}"
else
echo "You are sourcing ${BASH_SOURCE[0]}"
fi
you can do three different things with it:
$ ./testcode
You are executing ./testcode
This works if testcode has the right permissions and the right shebang. With a shebang of #!/bin/false, this outputs nothing and returns a code of 1 (false).
$ bash ./testcode
You are executing ./testcode
This completely disregards the shebang (which can even be missing) and it only requires read permission, not executable permission. This is the way to call bash scripts from a CMD command line in Windows (if you have bash.exe in your PATH...), since there the shebang machanism doesn’t work.
$ . ./testcode
You are sourcing ./testcode
This also completely disregards the shebang, as above, but it is a complete different matter, because sourcing a script means having the current shell execute it, while executing a script means invoking a new shell to execute it. For instance, if you put an exit command in a sourced script, you exit from the current shell, which is rarely what you want. Therefore, sourcing is often used to load function definitions or constants, in a way somewhat resembling the import statement of other programming languages, and various programmers develop different habits to differentiate between scripts meant to be executed and include files to be sourced. I usually don’t use any extension for the former (others use .sh), but I use an extension of .shinc for the latter. Your former colleague used a shebang of #!/bin/false and one can only ask them why they preferred this to a zillion other possibilities. One reason that comes to my mind is that you can use file to tell these files apart:
$ file testcode testcode2
testcode: Bourne-Again shell script, ASCII text executable
testcode2: a /bin/false script, ASCII text executable
Of course, if these include files contain only function definitions, it’s harmless to execute them, so I don’t think your colleague did it to prevent execution.
Another habit of mine, inspired by the Python world, is to place some regression tests at the end of my .shinc files (at least while developing)
... function definitions here ...
[ "$0" != "${BASH_SOURCE[0]}" ] && return
... regression tests here ...
Since return generates an error in executed scripts but is OK in sourced scripts, a more cryptic way to get the same result is
... function definitions here ...
return 2>/dev/null || :
... regression tests here ...
The difference in using #!/bin/false or not from the point of view of the parent shell is in the return code.
/bin/false always return a failing return code (in my case 1, but not sure if it is standard).
Try that :
./my_foo.sh //does nothing
echo $? // shows "1", a.k.a failing
./my_bar.sh //does nothing
echo $? // shows "0", a.k.a. everything went right
So, using #!/bin/false not only documents the fact that the script is not intended to be executed, but also produces an error return code when doing so.
I have a main_script.sh which will call sub_script.
I have a variable in sub_script which i would like to access in main script
I tried "export" and "env" with the variable but when i'm trying to echo it in main_script i'm not getting values.
for example:
sub_script.sh
export a=hello
echo $a
main_script.sh
$PGMHOME/sub_script.sh > output_file
echo $a
FYI : sub_script.sh is executing properly because I'm getting value of 'a' in output_file
But when I'm echoing the value of a in main_script, I'm not getting it.
p.s : I know I can assign the variable directly in main_sript.sh but this is just an example and i have big processing done in sub_script.sh
Environments (export-ed variables) are passed only "downwards" (from parent to child process), not upwards.
This means that if you want to run the sub-script from the main-script as a process, the sub-script must write the names-and-values somewhere so that the parent process (the main script) can read and process them.
There are many ways to do this, including simply printing them to standard output and having the parent script eval the result:
eval $(./sub_script)
There are numerous pitfalls to this (including, of course, that the sub-script could print rm -rf $HOME and the main script would execute that—of course the sub-script can simply do that directly, but it's even easier to accidentally print something bad than to accidentally do something bad, so this serves as an illustration). Note that the sub-script must carefully quote things:
#! /bin/sh
# sub-script
echo a=value for a
When evaled, this fails because value for a gets split on word boundaries and evals to running for a with a=value set. The sub-script must use something more like:
echo a=\'value for a\'
so that the main script's eval $(./sub_script) sees a quoted assignment.
If the sub-script needs to send output to standard output, it will need to write its variable settings elsewhere (perhaps to a temporary file, perhaps to a file descriptor set up in the main script). Note that if the output is sent to a file—this includes stdout, really—the main script can read the file carefully (rather than using a simple eval).
Another alternative (usable only in some, not all, cases) is to source the sub-script from the main script. This allows the sub-script to access everything from the main script directly. This is usually the simplest method, and therefore often the best. To source a sub-script you can use the . command:
#! /bin/sh
# main script
# code here
. ./sub_script # run commands from sub_script
# more code here
Parameters to script are passed like $1, $2 etc. You can call main_script.sh from sub_script.sh and call main_script.sh again.
main_script.sh
#!/bin/sh
echo "main_script"
./sub_script.sh "hello world!"
sub_script.sh
#!/bin/sh
if [ "${1}" = "" ]; then
echo "calling main_script"
./main_script.sh
else
echo "sub_script called with parameter ${1}"
fi
./main_script.sh
calling main_script
main_script
sub_script called with parameter hello world!
I worked on a Bash script for the last day or so and running and debugging it directly on the shell.
The final script will be executed when the Ubuntu server gets rebooted.
I have started testing this, but my script gives me a different result then what I was expected.
I have narrowed it down to an "or condition" and rewrote a more simpler script to test this anomaly:
A call to this script has been made in /etc/rc.local, with a redirection of the output to a log file (log/reboot.log).
I have this in my script (as a test):
#!/bin/bash
YESTERDAY=20131103
SYS_DATE=20131104
LAST_START=20131104
if [[ $LAST_START = $YESTERDAY || $LAST_START = $SYS_DATE ]];
then
echo "is equal"
else
echo "is not equal"
fi
Executing in the shell I get "is equal" (the right answer). After the reboot in the log I get "is not equal".
Could someone tell me why?
I am guessing here,
But do you realize your /bin/sh is not your SHELL.
In UBUNTU and Debian, /bin/sh is DASH, your login shell is BASH.
So it might be related to your syntax of [[ ]] which is BASH.
Did you right in your top of the script:
#!/bin/sh
or
#!/bin/bash
[[
The [[ builtin is a bashism, and has somewhat better-defined semantics
than [ (a.k.a. test). However, it is still quite reasonable to use [
instead, and portable scripts must do so. Note that argument handling
is not quite the same; as above, use = rather than ==.
See here:
https://wiki.ubuntu.com/DashAsBinSh
the right way to do stuff here
The best solution would be actually to put your script in /etc/init.d and link it to run level 6. Which is the run level executed when rebooting. You should consider reading man 8 init when you have got some spare time. It will help you understand how your system is starting and shuting down.
On linux using bash,
lets say I made two programs both called print_report.
(They are in different directories.)
Inside my .bashrc file, I have:
PATH="path/to/print_report1/:$PATH"
This allows me to type print_report anywhere and it will run one of the programs.
How can I have bash decide to use one or the other depending on the working directory?
for example,
If I'm currently in ~/project1/ and type print_report it will use /bin/foo/print_report
If I'm currently in ~/project2/ and type print_report it will use /bin/bar/print_report
You can't do that as such. Instead, write a wrapper script or function that checks the current directory and invokes the right command:
#!/bin/bash
if [[ $PWD == $HOME/project1/* ]]
then
/bin/foo/print_report "$#"
elif [[ $PWD == $HOME/project2/* ]]
then
/bin/bar/print_report "$#"
else
echo "Don't know how to print_report for $PWD"
fi
You can emulate preexec hooks à la zsh, using the DEBUG trap.
In that way, every time a command is executed, you can run a preexec hook to check $PWD, and adjust $PATH accordingly.
You can include a preexec hook doing what you want in your .bashrc.
This is a security disaster waiting to happen, (which is to say you really don't want to do this) but you can certainly do something like:
cd() {
dir=${1-.}
case $dir in)
path1) PATH=/path/for/working/in/path1;;
path2) PATH=/path/for/working/in/path2;;
*) PATH=/bin:/usr/bin;;
esac
command cd $dir
}
(Put that in your .bashrc or just define it in the current shell.)
Everything presented here so far strikes me as overly complicated and needlessly complex hackery. I would just place a Makefile in each directory with
report:
/bin/foo/print_report
in ~/project1/Makefile, and
report:
/bin/bar/print_report
in ~/project2/Makefile. This extends easily to as many directories and programs you want. And you only need to type make instead of those longwinded command names :-)
let's see an example: in my main.sh, I'd like to source a.sh and b.sh. a.sh, however, might have already sourced b.sh. Thus it will cause the codes in b.sh executed twice. Is there any mechanism alike "include guard" in C++?
If you're sourcing scripts, you are usually using them to define functions and/or variables.
That means you can test whether the script has been sourced before by testing for (one of) the functions or variables it defines.
For example (in b.sh):
if [ -z "$B_SH_INCLUDED" ]
then
B_SH_INCLUDED=yes
...rest of original contents of b.sh
fi
There is no other way to do it that I know of. In particular, you can't do early exits or returns because that will affect the shell sourcing the file. You don't have to use a name that is solely for the file; you could use a name that the file always has defined.
In bash, an early return does not affect the sourcing file, it returns to it as if the current file were a function. I prefer this method because it avoids wrapping the entire content in if...fi.
if [ -n "$_for_example" ]; then return; fi
_for_example=`date`
TL;DR:
Bash has a source guard mechanism which lets you decide what to do if executed or sourced.
Longer version:
Over the years working with Bash sourcing I found that a different approach worked excellently which I will discuss below.
The problem for me was similar to the one of the original poster:
sourcing other scripts led to double script execution
additionally, scripts are less testable with unit test frameworks like BATS
The main idea of my solution is to write scripts in a way that can safely sourced multiple times. A major part plays the extraction of functionality (compared to have a large script which would not render very testable).
So, only functions and global variables are defined, other scripts can be sourced at will.
As an example, consider the following three bash scripts:
main.sh
#!/bin/env bash
source script2.sh
source script3.sh
GLOBAL_VAR=value
function_1() {
echo "do something"
function_2 "completely different"
}
run_main() {
echo "starting..."
function_1
}
# Enter: the source guard
# make the script only run when executed, not when sourced)
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
run_main "$#"
fi
script2.sh
#!/bin/env bash
source script3.sh
ALSO_A_GLOBAL_VAR=value2
function_2() {
echo "do something ${1}"
}
# this file can be sourced or be executed if called directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
function_2 "$#"
fi
script3.sh
#!/bin/env bash
export SUPER_USEFUL_VAR=/tmp/path
function_3() {
echo "hello again"
}
# no source guard here: this script defines only the variable and function if called but does not executes them because no code inside refers to the function.
Note that script3.sh is sourced twice. But since only functions and variables are (re-)defined, no functional code is executed during the sourcing.
The execution starts with with running main.sh as one would expect.
There might be a drawback when it comes to dependency cycles (in general a bad idea): I have no idea how Bash reacts if files source (directly or indirectly) each other.
Personally I usually use
set +o nounset # same as set -u
on most of my scripts, therefore I always turn it off and back on.
#!/usr/bin/env bash
set +u
if [ -n "$PRINTF_SCRIPT_USAGE_SH" ] ; then
set -u
return
else
set -u
readonly PRINTF_SCRIPT_USAGE_SH=1
fi
If you do not prefer nounset, you can do this
[[ -n "$PRINTF_SCRIPT_USAGE_SH" ]] && return || readonly PRINTF_SCRIPT_USAGE_SH=1