One day, I was wondering how to share a function across multiple Bash scripts. So I asked a large language model (LLM) for advice. It suggested I place shared functions in a separate file and source that file from other scripts.
That made sense—modular code is reusable and avoids reinventing the wheel. The LLM also suggested I add the following guard at the top of the shared file:
# common_functions.sh
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
echo "Error: This file must be sourced, not executed." >&2
exit 1
fi
As an intermediate Linux user, I knew that $0
is the name of the script being executed. But BASH_SOURCE
? That was new to me. Time to dive into the Bash rabbit hole.
First Stop: The Manual
BASH_SOURCE
An array variable whose members are the source filenames where the corresponding shell function names in the FUNCNAME array variable are defined. The shell function \${FUNCNAME[\$i]} is defined in the file \${BASH_SOURCE[\$i]} and called from \${BASH_SOURCE[$i+1]}.
It seems that there is another array related to BASH_SOURCE
, which is FUNCNAME
. Judging from its name, we can guess that it might be used to keep track of the execution of functions. The man page tells us that our intuition is correct.
FUNCNAME
An array variable containing the names of all shell functions currently in the execution call stack. The element with index 0 is the name of any currently-executing shell function. The bottom-most element (the one with the highest index) is "main". This variable exists only when a shell function is executing. Assignments to FUNCNAME have no effect. If FUNCNAME is unset, it loses its special properties, even if it is subsequently reset.
This variable can be used with BASH_LINENO and BASH_SOURCE. Each element of FUNCNAME has corresponding elements in BASH_LINENO and BASH_SOURCE to describe the call stack. For instance, \${FUNCNAME[\$i]} was called from the file ${BASH_SOURCE[\$i+1]} at line number \${BASH_LINENO[\$i]}. The caller builtin displays the current call stack using this information.
Huh, it seems that there is another array related to BASH_SOURCE
, which is BASH_LINENO
. Although in the current section, we know what it means, but I still decided to learn more.
BASH_LINENO
An array variable whose members are the line numbers in source files where each corresponding member of FUNCNAME was invoked. \${BASH_LINENO[\$i]} is the line number in the source file (\${BASH_SOURCE[\$i+1]}) where \${FUNCNAME[\$i]} was called (or \${BASH_LINENO[\$i-1]} if referenced within another shell function). Use LINENO to obtain the current line number.
Interpreting the Call Stack
Putting it together:
${FUNCNAME[$i]}
is defined in${BASH_SOURCE[$i]}
.- It was called from
${BASH_SOURCE[$i+1]}
at line${BASH_LINENO[$i]}
.
This is similar to how call stacks work in languages like C.
To illustrate, consider this C program:
// file: foo.c
#include <bar.h>
void foo(void) {
bar();
return;
}
int main(void) {
foo();
return 0;
}
// file: bar.c
void bar(void) {
return;
}
Even though bar()
is defined in bar.c
, we say it's called from foo.c
because it's invoked in foo()
.
Likewise, in Bash:
${FUNCNAME[$i]}
is defined in${BASH_SOURCE[$i]}
- and called from
${FUNCNAME[$i+1]}
in${BASH_SOURCE[$i+1]}
.
Testing the Theory
I wrote three Bash scripts:
#!/bin/bash
echo "This is outer.sh"
. middle.sh
#!/bin/bash
echo "This is middle.sh"
. inner.sh
#!/bin/bash
func() {
echo "Length of BASH_SOURCE: ${#BASH_SOURCE[@]}"
for i in "${!BASH_SOURCE[@]}"
do
echo "\${BASH_SOURCE[$i]}: ${BASH_SOURCE[$i]}"
done
echo "Length of FUNCNAME: ${#FUNCNAME[@]}"
for i in "${!FUNCNAME[@]}"
do
echo "\${FUNCNAME[$i]}: ${FUNCNAME[$i]}"
done
echo "Length of BASH_LINENO: ${#BASH_LINENO[@]}"
for i in "${!BASH_LINENO[@]}"
do
echo "\${BASH_LINENO[$i]}: ${BASH_LINENO[$i]}"
done
}
func
Note: we must wrap the print logic in a function. Otherwise, FUNCNAME
won't be set.
Now run it:
$ ./outer.sh
This is outer.sh
This is middle.sh
Length of BASH_SOURCE: 4
${BASH_SOURCE[0]}: inner.sh
${BASH_SOURCE[1]}: inner.sh
${BASH_SOURCE[2]}: middle.sh
${BASH_SOURCE[3]}: ./outer.sh
Length of FUNCNAME: 4
${FUNCNAME[0]}: func
${FUNCNAME[1]}: source
${FUNCNAME[2]}: source
${FUNCNAME[3]}: main
Length of BASH_LINENO: 4
${BASH_LINENO[0]}: 23
${BASH_LINENO[1]}: 4
${BASH_LINENO[2]}: 4
${BASH_LINENO[3]}: 0
Interpreting the Output
Let’s decode the call stack:
- func is defined in inner.sh, and is called by source from inner.sh at line 33
- source is defined in inner.sh, and is called by source from middle.sh at line 4
- source is defined in middle.sh, and is called by main from ./outer.sh at line 4
- main is defined in ./outer.sh, and is called by ??? from ??? at line 0
Notice the problem? source
is a Bash builtin which is not defined by us, and main
is called by no one from nowhere because of out of array index. What's more, main
is not introduced in detail in the man page of Bash. So, the the rule we mentioned above is not exactly correct at these corner cases, but is still good enough to interpret the user-defined functions.
What Are source
and main
in FUNCNAME
?
These names aren’t user-defined functions. They are pseudo-functions inserted by Bash itself.
source
When you use source
(or .
), Bash internally pushes "source"
onto the call stack to track the context. It behaves as if the contents of the sourced file are being executed inside a function.
You can even see this in the Bash source code:
git clone https://git.savannah.gnu.org/git/bash.git
cd bash
grep -i "\"source\"" -nr *
Output:
builtins/evalfile.c:240: array_push (funcname_a, "source"); /* not exactly right */
builtins/mkbuiltins.c:158: ":", ".", "source", "break", "continue", "eval", "exec", "exit",
builtins/source.def:2:It implements the builtins "." and "source" in Bash.
builtins/source.def:168: begin_unwind_frame ("source");
builtins/source.def:197: run_unwind_frame ("source");
grep: po/ga.gmo: binary file matches
Even the comment in the code admits this is an imperfect abstraction.
main
The "main"
entry represents the top-level execution context—what runs when Bash starts interpreting a script. This is confirmed in shell.c
:
grep -i "\"main\"" -nr *
Output:
doc/bash.0:1004: tom-most element (the one with the highest index) is "main".
doc/bash.1:1838:.if t \f(CW"main"\fP.
doc/bash.1:1839:.if n "main".
doc/bash.info:5497: bottom-most element (the one with the highest index) is '"main"'.
doc/bash.ps:1854:10/Courier@0 SF("main")2.884 E F0 5.384(.T)C .384(his v)-5.384 F .385
grep: doc/bashref.dvi: binary file matches
doc/bashref.info:5498: bottom-most element (the one with the highest index) is '"main"'.
doc/bashref.ps:14195:(with)g(the)g(highest)630 1549 y(index\))e(is)h Ft("main")p
doc/bashref.texi:6396:is @code{"main"}.
make_cmd.c:801: initialized come from the environment. Otherwise default to "main"
make_cmd.c:804: temp->source_file = shell_initialized ? "main" : "environment";
shell.c:1640: array_push (funcname_a, "main");
So main
isn’t a real function. It just marks the start of your script execution. That’s why it appears at the bottom of the call stack.
Conclusion
While FUNCNAME
, BASH_SOURCE
, and BASH_LINENO
give you powerful introspection into Bash's call stack, they have some quirks:
FUNCNAME
may includesource
andmain
, even though they're not real functions.BASH_SOURCE
tracks sourced files just like includes in C.- Always wrap logic inside functions to get useful stack traces.
Understanding these variables can help you debug, audit, or even write tools in Bash that require deep introspection.
Comments NOTHING