Re: [PATCH] Restrict named directories to scalar parameters.
Philippe Altherr <[email protected]> Mon, 15 Jun 2026 02:23:31 +0200
| Newsgroups | gmane.comp.shells.zsh.devel |
|---|---|
| Message-ID | <CAGdYchsJEQOh57xfSXhJtEXefw3q=fOM8dQa0FmGRNa6eWXv4Q@mail.gmail.com> |
>
> Aside: I suggest adding PS1="" to the input of zsh -fis for the Test/K01
> patch.
What is the advantage? It doesn't seem to make any difference.
Sorry, the following is way too long for these two small patches but
hopefully it will make it clear where I'm coming from and let you pinpoint
what, if anything, you disagree with.
Here is my understanding of named directories minus the features not
relevant for the current discussion.
Zsh maintains a table of named directories that maps directory names to
directories. The table is accessible via the parameter nameddirs. The table
supports two features:
- *Filename abbreviation:* When Zsh prints a filename F (e.g.,
/foo123/bar), it checks whether the table contains a name N (e.g., foo)
whose directory D (e.g., /foo123) is a prefix of F. If yes, instead of
printing $F (i.e., /foo123/bar), it prints ~$N/${F#$D} (i.e., ~foo/bar).
- *Named directory expansion:* When Zsh expands a filename F that starts
with ~N, it checks whether the table contains a name N mapped to a
directory D. If no name N is found in the table, Zsh checks whether there
is a parameter P named N whose value is a directory D (i.e., any value that
starts with "/"). If yes, it adds the pair (N, D) to the table and flags
the parameter P with PM_NAMEDDIR. In both cases, ~N is replaced with D when
F is expanded.
Once a parameter P named N is flagged with PM_NAMEDDIR, each time it is
updated with a new value D, the pair (N, D) is added to the table of named
directories if D starts with "/" and otherwise any entry whose name is N is
removed from the table.
One can distinguish two kinds of named directories:
- *Hash-based:* When a named directory N is defined with the "hash"
command (e.g. "hash -d foo=/foo123), then it only lives in the table of
named directories; no parameter named N is needed and defining or updating
one before or after the call to "hash" has no effect.
- *Parameter-based:* When a named directory is defined by first defining
a parameter P named N and then expanding the filename ~N (e.g.,
"foo=/foo123; : ~foo"), then it's backed by the parameter P; any update of
the value of the parameter P also updates the named directory N.
With a parameter-based named directory N, one could expect that ~N always
expands to the same as $N but that isn't true if one calls "hash" after
defining the named directory:
% zsh -fic 'foo=/foo1; : ~foo; hash -d foo=/foo2; printf "\$foo=%s
~foo=%s\n" $foo ~foo'
$foo=/foo1 ~foo=/foo2
One could argue that the call to "hash" turned the parameter-based named
directory into a hash-based one but that isn't the case either. Indeed,
updating the parameter after calling "hash" still updates the named
directory:
% zsh -fic 'foo=/foo1; : ~foo; hash -d foo=/foo2; foo=/foo3; printf
"\$foo=%s ~foo=%s\n" $foo ~foo'
$foo=/foo3 ~foo=/foo3
To me this looks more like a bug than a feature. It could be addressed
either by updating the value of the parameter or by removing the
PM_NAMEDDIR flag from the parameter when "hash" is called. The former would
ensure that $N and ~N remain in sync, the latter would turn the
parameter-based named directory into a true hash-based one.
*My assumption is that for a parameter-based named directory N, it is in
principle expected that ~N and $N remain in sync.* The example above shows
that this can be violated. However, to the best of my knowledge, it is true
as long as one never uses "hash" to modify parameter-based named
directories.
When parameters are searched for the expansion of ~N (because there isn't
yet any N in the table of named directories), the search is restricted to
scalar parameters
<https://github.com/zsh-users/zsh/blob/8ca612cfe2ace3819656c483dcc3822da6b63f01/Src/utils.c#L1259>.
One could argue that the search should be expanded to named references that
refer to a scalar parameter. This was actually the case before workers/54475
<http://zsh.org/workers/54475>. However that broke the assumption above;
even in the absence of calls to "hash", ~N and $N could get out of sync:
% zsh -fic 'var=/foo1; typeset -n foo=var; : ~foo; foo=/foo2; printf
"\$foo=%s ~foo=%s\n" $foo ~foo'
$foo=/foo2 ~foo=/foo1
Fixing this without extra infrastructure would be way too expensive; every
update of a scalar parameter would have to scan the whole parameter table
to check whether there is a named reference flagged with PM_NAMEDDIR that
refers to it. The extra infrastructure looks way too overblown for such a
small feature. To me, restricting parameter-based named directories to
scalar parameters looks like the most pragmatic approach.
*Directory values*
When parameters are searched for the expansion of ~N, only parameters whose
expansion starts with a "/" are considered:
% zsh -fic 'foo=no-slash; : ~foo; printf "~foo=%s\n" ~foo'
zsh:1: no such user or named directory: foo
When parameters flagged with PM_NAMEDDIR are updated with a value that
doesn't start with a "/", the corresponding named directory is removed from
the table instead of being updated with the new value:
% zsh -fic 'foo=/foo1; : ~foo; printf "~foo=%s\n" ~foo; foo=no-slash;
printf "~foo=%s\n" ~foo'
~foo=/foo1
zsh:1: no such user or named directory: foo
If the "hash" command is used to define or update a named directory, then
surprisingly all values are accepted:
% zsh -fic 'hash -d foo=no-slash; printf "~foo=%s\n" ~foo'
~foo=no-slash
It's unclear to me whether the fact that the "hash" command doesn't
restrict values to ones that start with a "/" is a bug or a feature.
*AUTO_NAME_DIRS option*
The aim of the AUTO_NAME_DIRS
<https://zsh.sourceforge.io/Doc/Release/Options.html#index-AUTO_005fNAME_005fDIRS>
option
is to avoid the need to expand ~N in order to create a parameter-based
named directory N; one can simply define the parameter N.
A side-effect of the AUTO_NAME_DIRS option is that it makes it possible to
turn a hash-based named directory N into a parameter-based one. Indeed, if
a parameter named N is defined after the call to "hash", it will override
the value specified by the "hash" command and any further updates of the
parameter named N will be reflected in the named directory N:
% zsh -fic 'setopt autonamedirs; hash -d foo=/foo1; foo=/foo2; printf
"\$foo=%s ~foo=%s\n" $foo ~foo'
$foo=/foo2 ~foo=/foo2
The documentation of the AUTO_NAME_DIRS option
<https://zsh.sourceforge.io/Doc/Release/Options.html#index-AUTO_005fNAME_005fDIRS>
states
that only parameters whose value is an absolute directory become named
directories. However, the current implementation promotes all scalar
parameters and all named references to named directories (i.e., flags them
with PM_NAMEDDIR) whenever their value is initialized or updated.
As discussed above, parameter-based named directories backed by named
references aren't supported by the current implementation because it can't
ensure that ~N and $N remain in sync. An additional issue is that the code
that promotes named references to named directories initializes the named
directory with the name of the referred parameter instead of
initializing it with the value of the referred parameter.
A better description of the patch workers/54759
<http://zsh.org/workers/54759> is that it prevents the promotion of named
references to named directories. Given the current shortcomings and given
the fact that named references can never be initialized with values that
start with "/", the only noticeable effect of the patch is that
initializing a named reference N after creating a hash-based named
directory N, will no longer remove the named directory:
% zsh -fic 'setopt autonamedirs; hash -d foo=/foo1; typeset -n foo=var;
echo "nameddirs=(${(kv)nameddirs})"'
nameddirs=() # Before patch
nameddirs=(foo /foo1) # After patch
A better description of the patch workers/54760
<http://zsh.org/workers/54760> is that it prevents the promotion to named
directories of (scalar) parameters whose value doesn't start with a "/".
When AUTO_NAME_DIRS is disabled, declaring, initializing and/or updating a
parameter never has any effect on a hash-based named directory:
% zsh -fic 'hash -d foo=/foo1; foo=/foo2; : ~foo; ; echo "nameddirs
=(${(kv)nameddirs})"'
nameddirs=(foo /foo1)
When AUTO_NAME_DIRS is enabled, the same is still true for any non-scalar
and non-nameref parameter:
% zsh -fic 'setopt autonamedirs; hash -d foo=/foo1; foo=(/foo2); : ~foo; ;
echo "nameddirs=(${(kv)nameddirs})"'
nameddirs=(foo /foo1)
% zsh -fic 'setopt autonamedirs; hash -d foo=/foo1; typeset -i foo=1/2; :
~foo; ; echo "nameddirs=(${(kv)nameddirs})"'
nameddirs=(foo /foo1)
It is even true for scalar declarations with no initialization:
% zsh -fic 'setopt autonamedirs; hash -d foo=/foo1; typeset foo; : ~foo; ;
echo "nameddirs=(${(kv)nameddirs})"'
nameddirs=(foo /foo1)
Given the above and given the fact that the documentation of the
AUTO_NAME_DIRS option
<https://zsh.sourceforge.io/Doc/Release/Options.html#index-AUTO_005fNAME_005fDIRS>
states
that only parameters whose value is an absolute directory become named
directories, it seems reasonable to think that initializing or updating a
scalar parameter with a value that is not a directory (i.e., that doesn't
start with a "/") should leave any existing named directory unchanged:
% zsh-dev -fic 'setopt autonamedirs; hash -d foo=/foo1; foo=foo2; echo
"nameddirs=(${(kv)nameddirs})"'
nameddirs=() # Before patch
nameddirs=(foo /foo1) # After patch
and should not flag the parameter with PM_NAMEDDIR:
% zsh -fic 'setopt autonamedirs; foo=foo1; hash -d foo=/foo2; setopt +o
autonamedirs; foo=foo3; echo "nameddirs=(${(kv)nameddirs})"'
nameddirs=() # Before patch
nameddirs=(foo /foo2) # After patch
In other words, only scalar parameters that effectively store absolute
directory names are ever promoted to named directories.
Philippe