Reputation: 11269
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]}
Upvotes: 260
Views: 174899
Reputation: 437513
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
).
Upvotes: 368
Reputation: 8557
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
Upvotes: 1
Reputation: 480
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
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.
Upvotes: 30
Reputation: 28811
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 source
d.
Upvotes: 28
Reputation: 2166
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'
Upvotes: 58