89

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

Thanatos
  • 42,585
  • 14
  • 91
  • 146

7 Answers7

74

${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)

mcandre
  • 22,868
  • 20
  • 88
  • 147
Hui Zheng
  • 10,084
  • 2
  • 35
  • 40
  • 22
    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}` – mklement0 Feb 04 '15 at 22:13
  • 1
    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) – PierreBdR Jul 29 '15 at 12:21
  • Is there a documentation which other options apart form -%N are available? – Martin Jul 08 '16 at 09:14
  • 1
    Note: if your zshrc file is a symlink, you can use `readlink` to resolve to the absolute path: `readlink -f ${(%):-%N}` – Bryce Guinta Nov 29 '16 at 04:27
  • 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. – Jonathan Klabunde Tomer Aug 28 '19 at 17:42
  • 4
    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}` – basicdays Apr 13 '21 at 00:00
64

${(%):-%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.
Community
  • 1
  • 1
mklement0
  • 382,024
  • 64
  • 607
  • 775
25

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.

dols3m
  • 1,676
  • 2
  • 16
  • 25
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.

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
  • 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`. – Thanatos Mar 28 '12 at 20:44
  • @Thanatos 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? – Gilles 'SO- stop being evil' Mar 28 '12 at 20:51
  • 3
    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. – toxalot Mar 19 '14 at 22:09
  • 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? – David Faure Jan 09 '15 at 13:35
  • @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? – Gilles 'SO- stop being evil' Jan 09 '15 at 13:45
  • The latter, since that's the subject of this whole post. But I found it in http://stackoverflow.com/questions/11685135/how-to-get-parent-folder-of-executing-script-in-zsh : ${BASH_SOURCE:-$0} – David Faure Jan 09 '15 at 13:46
  • 1
    @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}`. – mklement0 Feb 04 '15 at 22:10
  • 2
    @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}`. – mklement0 Feb 04 '15 at 22:39
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.)

Community
  • 1
  • 1
Francis Potter
  • 1,629
  • 1
  • 17
  • 19
2

Maybe you're looking for $_?

# foo.sh
source foo2.sh

and

# foo2.sh
echo $_

yields

# ./foo.sh
foo2.sh
Coderer
  • 25,844
  • 28
  • 99
  • 154
  • 1
    for best result: `${BASH_SOURCE:-$_}` – mpapis Oct 23 '13 at 01:09
  • 1
    It's best-practice to explain *why* an answer you provide works, or, in cases like this where the answer is , to include a basic explanation of that feature. (hell, even copy-pasting a short section of the manpage is never a bad idea!) – ELLIOTTCABLE Dec 26 '14 at 00:03
  • $_ 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. – David Faure Jan 09 '15 at 13:37
  • 1
    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 http://linuxshellaccount.blogspot.com/2008/04/shell-special-variables-in-bash.html) – ACK_stoverflow May 19 '21 at 21:02
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`"
Samuel Neff
  • 73,278
  • 17
  • 138
  • 182