1

I use source (.) in $HOME/.bash_profile to execute commands from a local $HOME/.dotfiles/.bash_profile file:

[[ -r "$HOME/.dotfiles/.bash_profile" ]] && . "$HOME/.dotfiles/.bash_profile" 

Now, in $HOME/.dotfiles/.bash_profile, $0 has the value of "-bash", which means it cannot be used by dirname to retrieve the current directory of the executing script.

I've read that BASH_SOURCE should be populated with the name of the executing script (and it works!).

But why does BASH_SOURCE hold the name of the executing script, when it is defined in man bash as an array of source filenames corresponding to shell functions?

Futhermore, since BASH_SOURCE is defined to be an array, it's not safe to use it as follows to retrieve the current directory of the executing script, since it could hold multiple values: dirname $BASH_SOURCE?

man bash:

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]}.
1
2

But why does BASH_SOURCE hold the name of the executing script, when it is defined in man bash as an array of source filenames corresponding to shell functions?

Why? Probably because it's useful to have the name of the file holding the main script available too.

Let's look at the description in 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]}

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".

In function foo, called from the main script, ${FUNCNAME[0]} contains foo, and ${BASH_SOURCE[0]} contains the name of the file that defined foo. ${FUNCNAME[1]} contains main and ${BASH_SOURCE[1]} contains the name of the main script file.

For FUNCNAME, it's explicitly stated that outside a function it's empty. For BASH_SOURCE it's different. The extension of always having the name of the main script file at the end of BASH_SOURCE is somewhat logical: function calls and returns only need to add/remove elements at the front.

It doesn't say that, but BASH_SOURCE also tracks sourced scripts (. foo.sh or such). They also don't show in FUNCNAME as long as no function is called, but do show up as source when there's an actual function in front.

Futhermore, since BASH_SOURCE is defined to be an array, it's not safe to use it as follows to retrieve the current directory of the executing script, since it could hold multiple values: dirname $BASH_SOURCE?

I'm not sure if this is a question or a statement. But remember that referencing an array without an index is the same as using an index of 0. So $BASH_SOURCE is ${BASH_SOURCE[0]}, and contains the name of the file that defined the current function. (Not that of the main script, for that you need something like ${BASH_SOURCE[ ${#BASH_SOURCE[@]} - 1 ]}.)

Note that the file name might still be something like ./foo.sh or foo.sh, so dirname might not be able to tell anything useful.

8
  • Thank you. Very useful. What do you mean by “but do show up as source when there's an actual function in front.”?
    – Shuzheng
    Jul 31 '20 at 16:13
  • 1
    @Shuzheng, (1) I mean that if there's a sourced script that called a function foo, then FUNCNAME will contain foo, source, and maybe main. If foo.sh contains foo() { declare -p FUNCNAME; }; foo then bash -c '. foo.sh' prints declare -a FUNCNAME=([0]="foo" [1]="source"). (It doesn't include main or anything for commands from -c, for some reason.)
    – ilkkachu
    Jul 31 '20 at 16:32
  • 1
    @Shuzheng, (2) FUNCNAME doesn't always contain main, just if a function is being executed. Then it contains the function name, and main. (Except if the main script is from -c, in which case it's missing...) That's probably so that you can just check $FUNCNAME and get blank when outside a function. On the other hand, BASH_SOURCE does contain values even outside a function. Confusing. (3) yes, add/remove at the front of course.
    – ilkkachu
    Jul 31 '20 at 16:37
  • 1
    @Shuzheng, (4) it's not explicitly said as far as I can see, but it seems logical to have it there even when FUNCNAME is empty. Outside a function, $FUNCNAME doesn't make sense, but $BASH_SOURCE does. I don't want to go looking at the changelogs, but it does come to mind that perhaps the variables were just plain scalars at first, and then got extended to arrays, keeping the old behaviour.
    – ilkkachu
    Jul 31 '20 at 16:40
  • 1
    Since it hasn't been mentioned: BASH_SOURCE, FUNCNAME and BASH_LINENO seem to have been added for debugging purposes: tell the line at which a function was invoked, in which file, and in which file it was declared. BASH_SOURCE + BASH_LINENO are useful even outside functions, they show the line at which a file has been sourced (and how sourced files are stacked). To me, too, it looks like this is not explicitly stated in the doc.
    – fra-san
    Jul 31 '20 at 19:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.