Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
Re: [PATCH] Restrict named directories to scalar parameters.
- X-seq: zsh-workers 54778
- From: Mikael Magnusson <mikachu@xxxxxxxxx>
- To: Philippe Altherr <philippe.altherr@xxxxxxxxx>
- Cc: Bart Schaefer <schaefer@xxxxxxxxxxxxxxxx>, Zsh hackers list <zsh-workers@xxxxxxx>
- Subject: Re: [PATCH] Restrict named directories to scalar parameters.
- Date: Mon, 15 Jun 2026 06:04:27 +0200
- Arc-authentication-results: i=1; mx.google.com; arc=none
- Arc-message-signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:dkim-signature; bh=RE+RHngc9NB0lYW9LZsgkNSrH4nVRlaRUS5Mpssn8jw=; fh=PnDmAcAjRlVdxWLPBlUSPTE2cObmLUrHkAbKSKL6/zA=; b=d1zuQDkM3UVVfC3Cb8f/TFxn4bRopyFUMAU+eEW/yob+g6ZL7fancYfFH3QOdvoIyP tYE2Az4Tu1PiLoNj3c5TXSCWWbVnRKnsgb55an2VJjKL2N/XkNahw/K/73SFxYEVzrZa smV456WhXKAih0SXirirs+3and/O72a6tStk91Y/soNjQTl3DvxesYwo1MhQ2pLU2t38 5u2jIemd5HGxC9DwCXRL8v26TdVM4wBQZ0lgkTjmL4zoXWEULuDhyflZHoFvqtxlN89n z7XXcdoLorE2wP5jQO35Xc2xQ3ePbEDDoNXTmdgASYMeHYO47/XH1PH/rM3NoOsF41Ju 2MaA==; darn=zsh.org
- Arc-seal: i=1; a=rsa-sha256; t=1781496282; cv=none; d=google.com; s=arc-20240605; b=aW+JRh65DTebwqwMAB+h/rBNDssOFI5BY5dYYVM6O6prllXzKniHA0yg7mxMFlHpOl kDpb914y6AUQN7zmUUheNSKWiIM/oOoKNC7TPkEibpKWI5c0qOCKFWI54+XD8/GhRZ95 upKQzK9vyETWrnaL6diKnLkFej4e9LdvPrLE0vaqaa31bv5VgNckqoNm/Hqi3NxGyBG+ GWOE/0V6Ww68OCzde+1kBEJTDHLUOfjQmWy2gDiwyzVwvOkqHUBIvOVR6byVvRyPKYfh CB8HwqYLhleQ5niGt8p18l48W2OS/vB10pmz+w01nGptDZyoIcuTcmisJSOhEWUJ+Hav 9g5A==
- Archived-at: <https://zsh.org/workers/54778>
- In-reply-to: <CAGdYchsJEQOh57xfSXhJtEXefw3q=fOM8dQa0FmGRNa6eWXv4Q@mail.gmail.com>
- List-id: <zsh-workers.zsh.org>
- References: <CAGdYchsYTam9E4+h1nB2otg+5CDGki_57knH-=W+WAY6UiKGaA@mail.gmail.com> <CAH+w=7aHe64BUd6BMa7nbEnRXWEvTeSshUXZka+n_dA4DfvNBw@mail.gmail.com> <CAGdYchsJEQOh57xfSXhJtEXefw3q=fOM8dQa0FmGRNa6eWXv4Q@mail.gmail.com>
On Mon, Jun 15, 2026 at 2:23 AM Philippe Altherr
<philippe.altherr@xxxxxxxxx> wrote:
>>
>> 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.
My inline replies below attempt to describe the current behavior and
expectations of zsh, I'm not totally sure if you're trying to do that
or describe your ideal scenario.
> 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.
The table supports exactly one feature, mapping a string (the name) to
another string (the path). Things can be added to this table in
various ways, but there is always just one type of entry in the table.
The table has no knowledge of PM_NAMEDDIR at any point.
> 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.
One can do this, but zsh does not. There is still only one kind of
named directory. That there are parameters flagged with PM_NAMEDDIR is
orthogonal.
> 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.
There is still only one type of entry in the namedddirtab table, and
you can update it in various ways. There is no such thing as different
types of named directory. I'm not sure how you could argue that your
example breaking your assumption constitutes a bug. :)
> 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. 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. 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.
I agree that setting PM_NAMEDDIR on a nameref seems insane. It's a
property of a parameter that has a value. You can have a nameref
pointing to such a parameter, and then the nameddir would be updated
when you set its value, either through the parameter or through the
nameref. Right? It would be super duper weird otherwise. I don't think
anyone would ever expect that setting nameref -n foo=bar would set
~foo to the value of $bar (and even less so that ~foo would be set to
the literal string "bar").
> 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.
The manpage entry for the hash command sort of implies it is not the
primary interface to the hash table, but maybe more of a debug
feature? Though it doesn't say so outright:
hash [ -Ldfmrv ] [ name[=value] ] ...
hash can be used to directly modify the contents of
the command hash table,
and the named directory hash table. Normally one would
modify these tables
by modifying one's PATH (for the command hash table) or
by creating appropri‐
ate shell parameters (for the named directory hash
table). The choice of
> AUTO_NAME_DIRS option
>
> The aim of the AUTO_NAME_DIRS 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:
I'm sure I'm repeating myself at this point, but using "hash -d
foo=/bar" and "foo=/bar" will both set the value of ~foo to /bar,
there is no "type of named directory" in play at any point. You are
simply assigning to the same thing multiple times.
> % 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 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.
I can agree that the current behavior doesn't seem to agree with the manpage.
> 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.
I think there will be less confusion if you stop trying to think of
anything trying to keep things in sync. Assigning to a PM_NAMEDDIR
parameter will update the corresponding hash table entry, and the hash
command allows you to directly modify the hash table entry. It's like
saying that foo=$bar; foo=baz is a bug because bar doesn't have the
value baz now.
> A better description of the patch 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 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 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.
I agree with this conclusion on its own merit, it is what the
documentation already says it will do. (And the absolute directory
name doesn't have to actually exist, we just check for a leading
slash).
You might argue that someone will get confused that unset foo may now
leave a ~foo in place, but the only way to get into that situation
with this change would be if you manually inserted it with hash -d
foo=/bar, so I think we wouldn't be to blame for that confusion. Eg,
if ~foo had been added because foo was at some point /bar, then
unsetting foo would still remove the ~foo entry, because $foo has the
PM_NAMEDDIR flag.
That said, I would prefer using only the hash builtin to modify the
table and that it had nothing to do with parameters whatsoever. The
whole point of having a separate ~ namespace to me is that I can avoid
littering the $ namespace. Maybe while we're messing around in this
area, nobody would mind if I also add an option to disable ~foo
automagically checking if $foo has a path in it and setting ~foo,
since you currently cannot disable this?
--
Mikael Magnusson
Messages sorted by:
Reverse Date,
Date,
Thread,
Author