253

How does one choose between "$0" and "${BASH_SOURCE[0]}"

This description from GNU didn't help me much.

    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]}
3
  • 3
    BASH_SOURCE was added at bash-3.0-alpha. You may not have it, depending on your testing regime. I found it missing on both early Solaris and OS X. Also see return: can only `return' from a function or sourced script on U&L.SE.
    – jww
    Commented Oct 5, 2018 at 7:22
  • 39
    does anyone else think that 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]} is complete gibberish? I have no idea what that means...would be nice if someone explained what the docs/man page actually is trying to say. Commented Apr 23, 2021 at 20:14
  • 3
    The docs make more sense when I realized they were describing the bits and pieces necessary to construct a stack trace. Bash doesn't have a stack trace library itself (that I could find). Instead, I had to use FUNCNAME and BASH_SOURCE to build my own... Commented Sep 4, 2021 at 21:25

5 Answers 5

358

Note: For a POSIX-compliant solution, see this answer.

${BASH_SOURCE[0]} (or, more simply, $BASH_SOURCE[1]) contains the (potentially relative) path of the containing script in all invocation scenarios, notably also when the script is sourced, which is not true for $0.

Furthermore, as Charles Duffy points out, $0 can be set to an arbitrary value by the caller.
On the flip side, $BASH_SOURCE can be empty, if no named file is involved; e.g.:
echo 'echo "[$BASH_SOURCE]"' | bash

The following example illustrates this:

Script foo:

#!/bin/bash
echo "[$0] vs. [${BASH_SOURCE[0]}]"

$ bash ./foo
[./foo] vs. [./foo]

$ ./foo
[./foo] vs. [./foo]

$ . ./foo
[bash] vs. [./foo]

$0 is part of the POSIX shell specification, whereas BASH_SOURCE, as the name suggests, is Bash-specific.


[1] Optional reading: ${BASH_SOURCE[0]} vs. $BASH_SOURCE:

Bash allows you to reference element 0 of an array variable using scalar notation: instead of writing ${arr[0]}, you can write $arr; in other words: if you reference the variable as if it were a scalar, you get the element at index 0.

Using this feature obscures the fact that $arr is an array, which is why popular shell-code linter shellcheck.net issues the following warning (as of this writing):

SC2128: Expanding an array without an index only gives the first element.

On a side note: While this warning is helpful, it could be more precise, because you won't necessarily get the first element: It is specifically the element at index 0 that is returned, so if the first element has a higher index - which is possible in Bash - you'll get the empty string; try a[1]='hi'; echo "$a".
(By contrast, zsh, ever the renegade, returns all elements as a single string, separated with the first char. stored in $IFS, which is a space by default).

You may choose to eschew this feature due to its obscurity, but it works predictably and, pragmatically speaking, you'll rarely, if ever, need to access indices other than 0 of array variable ${BASH_SOURCE[@]}.


Optional reading, part 2: Under what conditions does the BASH_SOURCE array variable actually contain multiple elements?:

BASH_SOURCE only has multiple entries if function calls are involved, in which case its elements parallel the FUNCNAME array that contains all function names currently on the call stack.

That is, inside a function, ${FUNCNAME[0]} contains the name of the executing function, and ${BASH_SOURCE[0]} contains the path of the script file in which that function is defined, ${FUNCNAME[1]} contains the name of the function from which the currently executing function was called, if applicable, and so on.

If a given function was invoked directly from the top-level scope in the script file that defined the function at level $i of the call stack, ${FUNCNAME[$i+1]} contains:

  • main (a pseudo function name), if the script file was invoked directly (e.g., ./script)

  • source (a pseudo function name), if the script file was sourced (e.g. source ./script or . ./script).

9
  • so $BASH_SOURCE is more generic and works in more circumstances? Commented May 15, 2019 at 3:03
  • 6
    @AlexanderMills Yes, if you're using Bash, $BASH_SOURCE is the better choice.
    – mklement0
    Commented May 15, 2019 at 4:34
  • 2
    "(By contrast, zsh, ever the renegade, indeed does return the first element, irrespective of its index)." Actually, in zsh, an unsubscripted array parameter expansion becomes the whole array, not just the first item. Without shwordsplit set,$ary ~= "${ary[@]}", "$ary" ~= "${ary[*]}", and $=ary ~= ${ary[*]} or ${ary[@]} without the quotes.
    – Mark Reed
    Commented Jan 23, 2023 at 22:03
  • 1
    Thanks, @MarkReed - I've updated the answer to fix the incorrect statement, but I didn't go into as much depth as your comment does, given that the original comment was just an aside.
    – mklement0
    Commented Jan 23, 2023 at 22:35
  • 1
    I appreciate the nice feedback, @hek2mgl. The reason that $BASH_SOURCE is mentioned nonetheless is that not everyone uses shellcheck.net (notwithstanding its usefulness), and those that don't won't care about the latter's warnings. The middle section is for those who want to dig deeper and understand the trade-offs.
    – mklement0
    Commented May 24 at 23:23
57

These scripts may help illustrate. The outer script calls the middle script, which calls the inner script:

$ cat outer.sh
#!/usr/bin/env bash
./middle.sh
$ cat middle.sh
#!/usr/bin/env bash
./inner.sh
$ cat inner.sh
#!/usr/bin/env bash
echo "\$0 = '$0'"
echo "\${BASH_SOURCE[0]} = '${BASH_SOURCE[0]}'"
echo "\${BASH_SOURCE[1]} = '${BASH_SOURCE[1]}'"
echo "\${BASH_SOURCE[2]} = '${BASH_SOURCE[2]}'"
$ ./outer.sh
$0 = './inner.sh'
$BASH_SOURCE[0] = './inner.sh'
$BASH_SOURCE[1] = ''
$BASH_SOURCE[2] = ''

However, if we change the script calls to source statements:

$ cat outer.sh
#!/usr/bin/env bash
source ./middle.sh
$ cat middle.sh
#!/usr/bin/env bash
source ./inner.sh
$ cat inner.sh
#!/usr/bin/env bash
echo "\$0 = '$0'"
echo "\${BASH_SOURCE[0]} = '${BASH_SOURCE[0]}'"
echo "\${BASH_SOURCE[1]} = '${BASH_SOURCE[1]}'"
echo "\${BASH_SOURCE[2]} = '${BASH_SOURCE[2]}'"
$ ./outer.sh
$0 = './outer.sh'
$BASH_SOURCE[0] = './inner.sh'
$BASH_SOURCE[1] = './middle.sh'
$BASH_SOURCE[2] = './outer.sh'
0
28

For portability, use ${BASH_SOURCE[0]} when it is defined, and $0 otherwise. That gives

${BASH_SOURCE[0]:-$0}

Notably, in say zsh, the $0 does contain correct filepath even if the script is sourced.

4
  • It depends what you want it for. $BASH_SOURCE[-1] is probably more equivalent to $0 if you want the name of the script that is running rather than the file which has been sourced.
    – Tom Tanner
    Commented Jun 28, 2022 at 9:10
  • @TomTanner If we were considering solely bash, then your comment would be correct. But this is about compatibility with non-Bash shells. The whole point of this trick is that ${BASH_SOURCE[0]} gets evaluated to non-empty string (and becomes the result of the entire expression) in Bash, while the $0 only gets evaluated if the shell is not Bash (and does not define BASH_SOURCE). In ZSH, the equivalent of ${BASH_SOURCE[0]} is $0.
    – user7610
    Commented Jun 28, 2022 at 10:58
  • Yes, but just pointing out, ${BASH_SOURCE[-1]} is the equivalent of $0 in bash, ksh and (bourne) sh. I've run across this particular issue recently. ${BASH_SOURCE[0]} in a "source"d file refers to the name of the sourced file, which isn't always what you want
    – Tom Tanner
    Commented Jun 29, 2022 at 8:21
  • I did not test other shells than bash and zsh, so maybe the 'compatibility maneuver' that I intend can be done in a still more portable way
    – user7610
    Commented Jun 29, 2022 at 10:27
28

TL;DR I'd recommend using ${BASH_SOURCE:-$0} as the most universal variant.

Previous answers are good but they do not mention one caveat of using ${BASH_SOURCE[0]} directly: if you invoke the script as sh's argument and your sh is not aliased to bash (in my case, on Ubuntu 16.04.5 LTS, it was linked to dash), it may fail with BASH_SOURCE variable being empty/undefined. Here's an example:

t.sh:

#!/usr/bin/env bash

echo "\$0:                     [$0]"
echo "\$BASH_SOURCE:           [$BASH_SOURCE]"
echo "\$BASH_SOURCE or \$0:    [${BASH_SOURCE:-$0}]"
echo "\$BASH_SOURCE[0] or \$0: [${BASH_SOURCE[0]:-$0}]"

(Successfully) runs:

$ ./t.sh
$0:                    [./t.sh]
$BASH_SOURCE:          [./t.sh]
$BASH_SOURCE or $0:    [./t.sh]
$BASH_SOURCE[0] or $0: [./t.sh]

$ source ./t.sh
$0:                    [/bin/bash]
$BASH_SOURCE:          [./t.sh]
$BASH_SOURCE or $0:    [./t.sh]
$BASH_SOURCE[0] or $0: [./t.sh]

$ bash t.sh
$0:                    [t.sh]
$BASH_SOURCE:          [t.sh]
$BASH_SOURCE or $0:    [t.sh]
$BASH_SOURCE[0] or $0: [t.sh]

And finally:

$ sh t.sh
$0:                    [t.sh]
$BASH_SOURCE:          []
$BASH_SOURCE or $0:    [t.sh]
t.sh: 6: t.sh: Bad substitution

Resume

As you see, only the third variant: ${BASH_SOURCE:-$0} - works and gives consistent result under all invocation scenarios. Note that we take advantage of bash's feature of making a reference to an unsubscripted array variable equal to the first array element.

5
  • 3
    Using the unsubscripted array is a good idea, however using a bash feature in a shell script looking for POSIX compliance seems not very wise. It implies that you'll have different behaviors when sourced depending on the executing shell, thus it seems preferable to avoid that here and stick with $0 in POSIX 'mode', no ?
    – Zilog80
    Commented Jul 13, 2021 at 9:50
  • 1
    @Zilog80 Actually, I'm not seeking for POSIX compliance, there is another answer for it, I just offer the way to make it work under most probable (as I understand it) circumstances, not caring what shell dialect is actually hidden under /bin/sh (still, hoping, it'll be bash :)
    – it-alien
    Commented Jul 15, 2021 at 8:16
  • sh t.sh get fixed now. bash version 5.1.16
    – zw963
    Commented Oct 17, 2022 at 15:51
  • By using /bin/sh, you are seeking POSIX compliance.
    – CervEd
    Commented Aug 28, 2023 at 7:57
  • This is what I was looking for. It is the only setup which allows me to call my script from any working directory and have nested bash sources correctly resolve themselves. Thanks!
    – Mo Beigi
    Commented Feb 17 at 7:32
0

I always use $0, $BASH_SOURCE can be confusing.

Adding this to top of script file to ensure calling 'return' or 'exit' correctly because 'exit' in a sourced script may go to unintended consequence as it may make terminal quit.

# Ensure to use exit safely later
if [[ $BASH_SOURCE != $0 ]]; then
    echo "Don't source this file, Bash it."
    return
fi

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