Sourcing vs executing in Bash
Checking the value of $0 allows me to source rather than execute an entire script.
Today I wrote a script checksubmissions
to check submitted pull requests in the SAP-samples/devtoberfest-2021 repo related to Devtoberfest from SAP this year, specifically the Best Practices week.
In its current form, at the time of writing, the script follows a pattern that I've used for a while now
- some general settings and possibly global variables
- then some function definitions, including a
main
function definition - then finally, the invocation of that
main
function, passing on to it any parameters that were supplied when the script is invoked
For the sake of illustration, here's a super simplified script called myscript
that follows that pattern:
#!/usr/bin/env bash
set -o errexit
func1() {
echo "Inside func1"
}
main() {
echo "Running main with $*"
func1
}
main "$@"
Sometimes, especially when building out scripts like this, I like to test the functions individually, from the command line if possible. In the example from today, I check every pull request each time, initiated from main
like this:
main() {
getprs | while read -r number title; do
check "$number" "$title"
sleep 1
done
}
main "$@"
(Source)
But while developing, I wanted to test out the check
function (source) manually on a single pull request. Of course, editing the script to do that wasn't much of an effort, but I wondered if there was another way.
What if, from the shell prompt, I could source the script, to bring the function definitions into my current environment, and then manually invoke the check
function on a single pull request?
Sourcing the script as it is would have the unwanted effect of running checks on all the pull requests, because the last line in the script actually invokes main
, as it's supposed to.
It turns out that it is possible to determine whether a script is being sourced just by close examination of the $0
variable.
There's also the
BASH_SOURCE
environment variable, which I want to look into as well (e.g. by reading this StackOverflow post: Choosing between $0 and BASH_SOURCE) but that's for another time.
First, I can replace the simple invocation:
main "$@"
with this:
if [[ $0 =~ ^-bash ]]; then
return 0
else
main "$@"
fi
When the script is executed, the value of $0
is the name of the script. But when it's sourced, it's -bash
.
Now, if I implement this change in the super simplified myscript
above, then this is what happens, at a Bash shell prompt.
First, to show there's nothing up my sleeve, an attempt to invoke func1
fails:
; func1
-bash: func1: command not found
Now I execute the script, and it behaves as expected:
; ./myscript hello world
running main with hello world
Inside func1
Of course, we still don't have the func1
function available to us:
; func1
-bash: func1: command not found
But what if I source the script rather than execute it? I can do that with source myscript
or simply . myscript
:
; source myscript
Nothing seems to happen. Which is good -- we don't get the "running main with hello world" or "Inside func1" output.
But now the definition of func1
is available, and we can run it:
; func1
Inside func1
That seems rather appealing!
This is early days, I may have missed a fundamental gotcha, but for now, I've found a way (which vaguely reminds me of Python's if __name__ == "__main__"
pattern) to be able to reduce that (already small) gap even further between the interactive shell and script content.