问题
Recently I wanted to know the best way to get the path to the currently running script/file in Bash.
I found a lot of answers. In particular one was:
"${BASH_SOURCE:-${(%):-%x}}"
I remember deciding at the time that this was the best solution for my needs. Now I'm trying to remember why. I know what some of the pieces mean, but not all. And even with what I do know I can't put it all together to make any sense of it. It works. But why?
Every effort I've made to find this specific code anywhere on the web (including searching for bits of it) has failed, so I can't find where I found it originally.
Can anyone tell me what each piece means (and perhaps even suggest why I might have chosen this over other answers)?
Thanks!
UPDATE next day: great answer (and comments) from @rici which explain everything, including why I chose it.
It seems I chose this entire expression to enable the sourced files I'm using it in to be used reliably in both bash and zsh - which was and is a goal. With the dual substitutions, the entire expression gets the same answer in either shell.
Explains why I couldn't make any sense of (or find anything sensible via google for) the ${(%):-%x} part in bash because... well... it doesn't make much sense in bash, lol, since it's for zsh. I'm now adding a zsh tag to this for that reason.
Second UPDATE an hour later, in case this helps anyone: I've now tracked down this:
${BASH_SOURCE[0]} equivalent in zsh?
... which is where I got the ${(%):-%x}
from originally, most notably this specific answer:
https://stackoverflow.com/a/28336473/4516124
回答1:
This peculiar expression is attempting to provide the same result in bash and in zsh: the filepath of the currently running script.
In bash,
"${BASH_SOURCE:-${(%):-%x}}"
means "$BASH_SOURCE
if it exists and is not empty and otherwise ${(%):-%x}
". As long as you use this in a context in which $BASH_SOURCE
is defined as a non-empty value, the otherwise invalid substitution ${(%):-%x}
will never be used and bash won't complain about it. [Note 1]
For a detailed explanation of $BASH_SOURCE
please see this excellent answer by @mklement0.
Now, in zsh the variable $BASH_SOURCE
is normally not defined (although I suppose it might be exported from the parent), so the substitution does happen and zsh then replaces it with the expansion ${(%):-%x}
. Bash users might find that one even more mysterious, but we can take it apart as follows:
${...:-...}
means the same thing as in bash: use the right hand part if the left hand part is empty or undefined. Unlike bash, zsh allows the left-hand part to be completely empty, not just a variable whose value is empty. So{:-foo}
is a complicated way of writingfoo
.${(flags)...}
causes the specified flags to be evaluated on the expansion. In this case, we have the flag%
, which means that "prompt expansion" should be done on the parameter expansion. [Note 2]Without the flags are handled, we're left with
{:-%x}
; as indicated in point 1, this is equivalent to the string%x
. But the(%)
flag triggers prompt expansion of%x
. And in zsh prompt expansion,%x
is -- wait for it -- the name of the currently executing script file.
In short, in zsh, ${(%):-%x}
means (almost) exactly the same thing as $BASH_SOURCE
does in bash. And therefore, ${BASH_SOURCE:-${(%):-%x}}
expands to the current script source file in both bash and zsh.
Notes
If
$BASH_SOURCE
is undefined or empty, on the other hand, this will produce a "bad substitution". As we'll see in a moment,${(flags)...}
is a zsh-specific syntax, but that's not why bash complains about it. To bash, it looks like a suffix deletion (the%
operator) of the non-existent parameter$(
. And it complains because$(
is not a valid parameter name.For comparison, consider the very similar but valid substitution
${#%):-%x}
. That has the same value as$#
, because$#
doesn't end with):-%x
. That parse might be surprising to human eyes, but bash sees it plain as day.Zsh, like bash, has extremely customisable prompts using special escape sequences in one of the prompt variables. (Posix standard prompt variables are
$PS1
,$PS2
and$PS4
; both bash and zsh offer several others.) Bash offers some special backslash sequences (\u
is the current username, for example), and also allows arbitrary parameter expansions to be interpreted when the prompt is expanded. Zsh also has a variety of escape sequences, but uses%
as an escape character (%n
is the current username), and allows parameter expansions if theprompt_subst
shell option is set.Note that you usually can't experiment with
zsh
prompts by simply setting$PS1
, as you can in bash, because mostzsh
installations use "prompt themes". Once prompt themes are enabled, you need to create your own theme in order to change the prompt strings, because the theme settings are automatically applied before every command.
回答2:
${parameter:-word}
expands to word
if parameter
is unset. For example:
$ unset a b
$ c=123
$ echo ${a:-${b:-$c}}
123
Here both a
and b
are unset, therefore c
is expanded. The parameter expansion ${(%):-%x}
gives a bad substitution
error. This is because $(
is not a valid parameter. Bash manual defines parameter as:
A parameter is an entity that stores values. It can be a name, a number, or one of the special characters listed below. A variable is a parameter denoted by a name. A variable has a value and zero or more attributes. Attributes are assigned using the declare builtin command (see the description of the declare builtin in Bash Builtins).
and name as:
name: A 'word' consisting solely of letters, numbers, and underscores, and beginning with a letter or underscore. 'Name's are used as shell variable and function names. Also referred to as an 'identifier'.
来源:https://stackoverflow.com/questions/60782870/in-bash-what-is-the-meaning-of-every-piece-of-bash-source-x