${BASH_SOURCE[0]} equivalent in zsh?
Asked Answered
C

7

91

The title should say it all. I'm looking for an equivalent to ${BASH_SOURCE[0]} in zsh.

Note: I keep finding "$0 is equivalent to ${BASH_SOURCE[0]}" around the Internet, but this seems to be false: $0 seems to be the name of the executing command. (It's argv[0], which makes sense.) Echoing $0 in my script (.zshrc) gives zsh for $0, which isn't the same as what ${BASH_SOURCE[0]} is. In fact, ${BASH_SOURCE[0]} seems to work in zsh, except for inside .zshrc files.

What I'm really doing in my .zshrc (that isn't working):

echo ${BASH_SOURCE[0]}
source `dirname $0`/common-shell-rc.sh

The source fails ($0 is zsh) and the echo outputs a blank line.

Edit: apparently, for $0 to work, I need the option FUNCTION_ARGZERO option set. Any way to test if this is set in a script? (so that I can temporarily set it) It is apparently on unless you set nofunction_argzero, and it is on in my shell. Still get nothing for $0. (I think b/c I'm not in a function.)

Cretan answered 28/3, 2012 at 4:33 Comment(0)
B
76

${BASH_SOURCE[0]} equivalent in zsh is ${(%):-%N}, NOT $0(as OP said, the latter failed in .zshrc)

Here % indicates prompt expansion on the value, %N indicates "The name of the script, sourced file, or shell function that zsh is currently executing,

whichever was started most recently. If there is none, this is equivalent to the parameter $0."(from man zshmisc)

Bela answered 24/4, 2014 at 4:3 Comment(7)
Great - this should work for the OP, but note that the true $BASH_SOURCE equivalent is %x, not %N, as it refers to the enclosing file even when called inside of functions - in other words: use ${(%):-%x}Gasconade
Hey, reading the documentation, I don't understand why we need the :- in here, and why ${(%)%N} wouldn't work the same (note: I am aware it doesn't work)Tillo
Is there a documentation which other options apart form -%N are available?Hiss
Note: if your zshrc file is a symlink, you can use readlink to resolve to the absolute path: readlink -f ${(%):-%N}Reflect
PierreBdR: ${(%)%N} is like ${%N}, but subject to the (%) flag. ${%N} would attempt to expand the variable with an empty-string name (which isn't a real thing, but always successfully expands to nothing) and remove a trailing N. Using ${(%):-%N} attempts to expand that same empty-string variable, and then since that's empty, uses %N as a default value... which is then expanded like a prompt because of the (%) flag.Ellenaellender
If you want an even more terse way of finding the absolute path of the directory containing the source code, this will handle that: ${${(%):-%x}:A:h}Vigorous
@Tillo Prompt expansion works on the results of the parameter expansion, not the name of the parameter. You could do foo=%N followed by ${(%)foo}, but that's two steps. ${(%):-%N} removes the need to define a named parameter by using a null parameter name (which can't have any value) with the :- default-value syntax.Dolorous
G
70

${(%):-%x} is the closest zsh equivalent to bash's $BASH_SOURCE (and ksh's ${.sh.file}) - not $0.

Tip of the hat to Hui Zheng for providing the crucial pointer and background information in his answer.

It returns the (potentially relative) path of the enclosing script,

  • regardless of whether the script is being sourced or not.
    • specifically, it also works inside initialization/profiles files such as ~/.zshrc (unlike $0, which inexplicably returns the shell's path there).
  • regardless of whether called from inside a function defined in the script or not (unlike $0, which returns the function name inside a function).

The only difference to $BASH_SOURCE I've found is in the following obscure scenario - which may even be a bug (observed in zsh 5.0.5): inside a function nested inside another function in a sourced script, ${(%):-%x} does not return the enclosing script path when that nested function is called (again) later, after having been sourced (returns either nothing or 'zsh').


Background information on ${(%):-%x}:

  • (%):- in lieu of a variable name in a parameter (variable) expansion (${...}) makes escape sequences available that are normally used to represent environmental information in prompt strings, such as used in the PS1 variable to determine the string displayed as the primary interactive prompt.

    • % is an instance of a parameter expansion flag, all of which are listed in man zshexpn under the heading Parameter Expansion Flags.
  • %x is one of the escape sequences that can be used in prompt strings, and it functions as described above; there are many more, such as %d to represent the current dir.

    • man zshmisc lists all available sequences under the heading SIMPLE PROMPT ESCAPES.
Gasconade answered 5/2, 2015 at 4:53 Comment(2)
Is there a documentation which other options apart form -%x are available?Hiss
I've seen ${(%):-%N} used in other places, I just realized why ${(%):-%x} is better: if you are inside a function, %N will return the name of the function, like $0 with FUNCTION_ARGZERO does. %x returns the name of the script, like ${BASH_SOURCE[0]} does.Oribella
G
27

If you want to make your script both bash and zsh-compatible you can use ${BASH_SOURCE[0]:-${(%):-%x}}. The resulting value will be taken from BASH_SOURCE[0] when it's defined, and ${(%):-%x}} when BASH_SOURCE[0] is not defined.

Glossary answered 18/2, 2019 at 21:40 Comment(0)
S
11

$0 is correct. In a sourced script, this is the name of a script, as it was passed to the . or source built-in (so if the path_dirs option is set, you may need to do a $path lookup to find the actual location of the script).

.zshrc is not sourced, which explains why $0 is not set to .zshrc. You know the file name and location anyway: it's ${ZDOTDIR-~}/.zshrc.

Sandbag answered 28/3, 2012 at 20:7 Comment(8)
I'm still not sure this is correct. Create these two files: foo containing source ./foo2 and foo2 containing echo $0. Now run ./foo, and you'll get the output of ./foo, which is not the name of the sourced script. Replace $0 with ${BASH_SOURCE[0]}, re-run with bash, and you'll get ./foo2.Cretan
@Cretan I get ./foo2 (zsh 4.3.10; this may have changed at some point, but not since 4.0). Compared with your instructions, I added #!/bin/zsh at the top of foo, to run the script under zsh. Are you sure you're running foo under zsh?Exhilaration
Just to clarify and emphasize... In zsh (and possibly other shells), the value of $0 is the sourced script not the caller script. This is NOT how it works in Bash. So $0 in zsh is the same as ${BASH_SOURCE[0]} in Bash.Smutch
Thanks toxalot, that's clear. But then - what is the portable way of doing this, in a shell script that should work in both bash and zsh then? No way?Secretarial
@DavidFaure The portable way of doing what? Do you want to find the path to the master script, to the sourced script in which the current code is, or what?Exhilaration
The latter, since that's the subject of this whole post. But I found it in stackoverflow.com/questions/11685135/… : ${BASH_SOURCE:-$0}Secretarial
@Gilles: .zshrc is sourced - as all initialization/profile files are by their very purpose. Aside from that, zsh normally ALWAYS reports the script path in $0, irrespective of whether the script is being sourced or not. Apparently, the one exception are initialization/profile files, where zsh behaves like bash and ksh, setting $0 to the shell's path (due to sourcing). (Thus, if anything, the fact that $0 doesn't reflect the script path in this case is another indicator that .zshrc is being sourced.) The true zsh equivalent to $BASH_SOURCE is ${(%):-%x}.Gasconade
@toxalot: $0 in zsh is the equivalent of $BASH_SOURCE in most, but not all situations; the exceptions are: (a) in initialization/profile files (e.g., ~/.zshrc) $0 inexplicably reports the shell's path, and (b) inside a function it reports the function's name. Again, the true equivalent is ${(%):-%x}.Gasconade
M
10

If you are symlinking to .zshrc in a dotfiles directory and want to reference other files in the directory, then try this:

SOURCE=${(%):-%N}
while [ -h "$SOURCE" ]; do
  DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
done
DOTFILES_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"

(I got the loop script from here.)

Matroclinous answered 21/10, 2014 at 16:54 Comment(1)
Is there a documentation which other options apart form -%N are available?Hiss
W
2

Maybe you're looking for $_?

# foo.sh
source foo2.sh

and

# foo2.sh
echo $_

yields

# ./foo.sh
foo2.sh
Welles answered 27/8, 2013 at 8:28 Comment(4)
for best result: ${BASH_SOURCE:-$_}Metalwork
It's best-practice to explain why an answer you provide works, or, in cases like this where the answer is <X feature>, to include a basic explanation of that feature. (hell, even copy-pasting a short section of the manpage is never a bad idea!)Phosphor
$_ doesn't work in bash for the case where script1.sh sources script2.sh which then does echo $_. It will return script1.sh, i.e. $0 (the main script), while ${BASH_SOURCE[0]} return script2.sh (the subscript that is calling echo $_). So $_ is basically just like $0 AFAICS.Secretarial
For anyone else wondering WTF $_ is: "The _ variable is set to the absolute file name of your shell when you start it up (e.g. $_ = /bin/bash) or the script being executed if it's passed in an argument list when the shell is invoked. After that, it always expands to the value of the last command executed, or argument typed" (from linuxshellaccount.blogspot.com/2008/04/…)Pileum
B
1

Putting it all together for something that works in both bash and zsh:

SCRIPT_FILE_REL_PATH="${BASH_SOURCE[0]}"
if [[ "$SCRIPT_FILE_REL_PATH" == "" ]]; then
  SCRIPT_FILE_REL_PATH="${(%):-%N}"
fi
SCRIPT_FILE_PATH="`realpath $SCRIPT_FILE_REL_PATH`"
SCRIPT_DIR="`dirname $SCRIPT_FILE_PATH`"
Brandy answered 11/8, 2023 at 19:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.