2

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:

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?

2

Yes, your interpretation is correct (from skimming your long question).

Parameter expansion flags apply to parameter expansions. When you want it applied to any arbitrary string instead, you need to either store that string in a variable as in:

var=%1N
filename=${(%)var}

Or (as a hack) you can use the ${var:-string} parameter expansion and leave the var part empty: ${(%):-%1N}. That's a common trick(see 1 2 3 4 5 6 7 8 9 10 11 12 13 here for instance), though that makes for pretty illegible code. Alternatively, you could use the ${param+string} syntax and use a parameter like $- or $0 or $# that is always set (${(%)-+%1N}). That's neither shorter nor more legible though.

Here, you can also use print -P %1N

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.