I've been mucking around with my zsh configuration and am confused on a couple of points with zsh expansion syntax. A lot of this is based off things I've found at the following resources:
- http://zsh.sourceforge.net/Doc/Release/Expansion.html
- http://zsh.sourceforge.net/Doc/Release/Prompt-Expansion.html
- https://git.madduck.net/etc/zsh.git
- https://github.com/aspiers/shell-env/
- https://stackoverflow.com/questions/9901210/bash-source0-equivalent-in-zsh
Configuration
My current zsh configuration looks like this:
~/.config/zsh/rcsentry
~/.zshenv symlink -> ~/.config/zsh/rcsentry
~/.config/zsh/.zshenv symlink -> ~/.config/zsh/rcsentry
~/.config/zsh/.zshrc symlink -> ~/.config/zsh/rcsentry
~/.config/zsh/.zlogin symlink -> ~/.config/zsh/rcsentry
~/.config/zsh/.zprofile symlink -> ~/.config/zsh/rcsentry
My rcsentry is currently stripped down to a minimal state, while I try to understand exactly how this works. It currently contains:
if [[ -o rcs ]]; then
rcs_fn="${(%):-%1N}"
rcs_fp="${(%):-%N}"
echo "rcs_fn: $rcs_fn"
echo "rcs_fp: $rcs_fp"
if [[ "$rcs_fn" == ".zshenv" ]]; then
echo "initializing XDG environment variables ..."
export XDG_CACHE_HOME="${XDG_CACHE_HOME:=$HOME/.cache}"
export XDG_CONFIG_HOME="${XDG_CONFIG:=$HOME/.config}"
export XDG_DATA_HOME="${XDG_DATA_HOME:=$HOME/.local/share}"
echo "initializing ZSH environment variables ..."
export ZDOTDIR="${ZDOTDIR:=$XDG_CONFIG_HOME/zsh}"
fi
unset rcs_fn
unset rcs_fp
fi
When executing a new shell, I get the output I would expect:
rcs_fn: .zshenv
rcs_fp: /Users/me/.zshenv
initializing XDG environment variables ...
initializing ZSH environment variables ...
rcs_fn: .zshrc
rcs_fp: /Users/me/.config/zsh/.zshrc
rcs_fn: .zlogin
rcs_fp: /Users/me/.config/zsh/.zlogin
Documentation & Interpretation
I've got a couple things I'm confused about, but right now I'm trying to limit it to explicitly understanding the following:
I'm starting from the following syntax:
rcs_fn="${(%):-%1N}"
Initially I didn't understand what this meant at all. Even trying to reference the docs to figure it out is challenging. I know what it does - but I'm trying to figure out how to understand the syntax. So - starting from the docs, I've found the following:
If the opening brace is directly followed by an opening parenthesis, the string
up to the matching closing parenthesis will be taken as a list of flags.
-- `man zshexpn` -> PARAMETER EXPANSION -> Parameter Expansion Flags
I believe that this indicates that (%)
is a list of flags being used in a paremeter expansion statement.
% Expand all % escapes in the resulting words in the same way as in prompts
(see EXPANSION OF PROMPT SEQUENCES in zshmisc(1)). If this flag is given twice,
full prompt expansion is done on the resulting words, depending on the setting
of the PROMPT_PERCENT, PROMPT_SUBST and PROMPT_BANG options.
-- `man zshexpn` -> PARAMETER EXPANSION -> Parameter Expansion Flags
I believe that this indicates that (%)
is in fact a parameter expansion flag, telling zsh that the entire statement should have all % signs expanded as normally occurs in a prompt string.
%N 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. An integer may follow the `%'
to specify a number of trailing path components to show; zero means the
full path. A negative integer specifies leading components.
-- `man zshmisc` -> SIMPLE PROMPT ESCAPES
Given the previous assumptions, I believe this indicates that %1N
is a prompt expansion, which expands to the name of the file that contains the code currently being executed by zsh.
${name:-word}
If name is set, or in the second form is non-null, then substitute its value;
otherwise substitute word. In the second form name may be omitted, in which
case word is always substituted.
-- `man zshexpn` -> PARAMETER EXPANSION
I believe that this could indicate that the overall syntax of the statement is a word substitution. If that were the case, then I believe the name value is empty (the only thing before :
would be the expansion flags), and therefore the second part %1N
is automatically inserted. This seems really wierd to me though, like I'm interpretting the syntax completely wrong somehow.
7. Modifiers
Any modifiers, as specified by a trailing `#', `%', `/' (possibly doubled) or
by a set of modifiers of the form `:...' (see the section `Modifiers' in the
section `History Expansion'), are applied to the words of the value at this
level.
-- `man zshexpn` -> PARAMETER EXPANSION -> Parameter Expansion Flags -> Rules
After the optional word designator, you can add a sequence of one or more of the
following modifiers, each pre ceded by a `:'. These modifiers also work on the
result of filename generation and parameter expansion, except where noted.
-- `man zshexpn` -> HISTORY EXPANSION -> Modifiers
I'm really not quite sure how to interpret this, or if it is at all relevant. It appears that it could indicate that :
indicates the preceding (@)
is a word modifier and that the trailing -%1N
is either a filename generation syntax (doesn't make sense to me) or a parameter expansion syntax (but again doesn't make sense, I don't see %N listed in the parameter expansion section, only in the prompt expansion section).
Summary
Based on what I read in the docs, I think it breaks down as follows:
:- indicates a word expansion, with word section empty and substitution `%1N`
(%) parameter expansion flag, indicating the expression should be expanded as
if it were in prompt expansion
%1N a prompt escape, which expands to the name of the file
${..} explicit parameter expansion, required for parameter expansion flag
Testing
Playing around with this, I think I've kind of got the syntax figured out. My updated rcsentry file contains the following:
if [[ -o rcs ]]; then
# The name of the file containing currently code that is currently executing.
rcs_fn="${(%):-%1N}"
# The path of the file containing currently code that is currently executing.
rcs_fp="${(%):-%N}"
# The resolved path of the file containing currently code that is currently
executing.
rcs_x1="${${(%):-%N}:A}"
# The resolved path of the parent directory of the file containing currently
# code that is currently executing.
rcs_x2="${${${(%):-%N}:A}:h}"
if [[ "$rcs_fn" == ".zshenv" ]]; then
# echo "initializing XDG environment variables ..."
export XDG_CACHE_HOME="${XDG_CACHE_HOME:=$HOME/.cache}"
export XDG_CONFIG_HOME="${XDG_CONFIG:=$HOME/.config}"
export XDG_DATA_HOME="${XDG_DATA_HOME:=$HOME/.local/share}"
# echo "initializing ZSH environment variables ..."
export ZDOTDIR="${ZDOTDIR:=$XDG_CONFIG_HOME/zsh}"
fi
echo "rcs_fn: $rcs_fn"
echo "rcs_fp: $rcs_fp"
echo "rcs_x1: $rcs_x1"
echo "rcs_x2: $rcs_x2"
unset rcs_fn
unset rcs_fp
fi
Output is as expected:
rcs_fn: .zshenv
rcs_fp: /Users/me/.zshenv
rcs_x1: /Users/me/.config/zsh/rcsentry
rcs_x2: /Users/me/.config/zsh
rcs_fn: .zshrc
rcs_fp: /Users/me/.config/zsh/.zshrc
rcs_x1: /Users/me/.config/zsh/rcsentry
rcs_x2: /Users/me/.config/zsh
rcs_fn: .zlogin
rcs_fp: /Users/me/.config/zsh/.zlogin
rcs_x1: /Users/me/.config/zsh/rcsentry
rcs_x2: /Users/me/.config/zsh
So based on all that, how wrong am I?