Re: Issue with VAR=foo cmd where VAR is a named reference

Philippe Altherr <[email protected]> Tue, 14 Jul 2026 04:14:42 +0200
Newsgroups gmane.comp.shells.zsh.devel
Message-ID <CAGdYchtVybxzCq7_v9WTYR7g3Z1Re+i+iH1LwHTvM2fW_b=OXA@mail.gmail.com>
>
> The reasoning behind my suggestion (above)
> was that inline assignments should always yield something that
> actually goes into the environment:  the -x flag in the "as if local
> -x" is important, and your proposal discards it in the case of
> references.


OK, I see your point. If the command is an external command, then if the inline
assignment doesn't export the assigned value, it has no effect and could as
well not be there. So it makes sense to expect that an inline assignment
always exports the assigned parameter. My suggestions in workers/54948
<http://zsh.org/workers/54948> is actually based on the same reasoning. If
var is a string that isn't already flagged for export, then running
"var[n]=val cmd" is equivalent to running just "cmd", even though it looks
likely that the user expected the equivalent of "(var[n]=val; export var;
cmd)".

Does the current Zsh do what you suggest? Not really. It is true that if
"var" is an array then "var=str cmd" turns "var" into a string and exports
it but "var+=str cmd" doesn't change the type of "var" and doesn't export
anything, same for "var[2]=str cmd". There is nothing in the code that
indicates an intention to always export something. Quite on the contrary,
there is explicit code to avoid exporting anything new in case the assigned
value isn't a string, like in "var=(x) cmd" or if the assigned parameter
has a subscript.

It may well be that inline assignments were initially introduced to run
external commands with an enhanced/modified environment. However, inline
assignments can also be used with user defined functions. In that context,
it's not uncommon that the main/only desired effect is the temporary
modification of the value of the assigned parameter; the flagging of the
assigned parameter for export may not be needed/desired at all.

One case where I found that inline assignments are particularly useful is
for complex recursive functions that depend on many parameters and where
most recursive calls only need to change a subset of all the parameters. In
that case, it's much cleaner to declare all the parameters outside of the
function and perform (recursive) calls with inline assignments to modify
the parameters that need a different value. This is also much more flexible
than to pass all parameters via positional parameters. Indeed, with
external parameters, there is no difficulty to define recursive functions
that depend on multiple array parameters, while with positional parameters,
calls would be way more complex as they would have to encode all the arrays
into a single sequence of strings. This way of passing parameters also has
the advantage that integer and float parameters don't have to be converted
to strings and then back to integers and floats. In that context, whether
inline assignments export anything is irrelevant but it's important that
they preserve the type of the assigned parameters, which is what the
current implementation does (except if you run "arr=str cmd" but that's no
different from running just "arr=str").

When inline assignments are used as a mechanism for passing typed
parameters in function calls, it becomes interesting to also be able to
pass references in the same way. That's exactly what my proposal for inline
assignments of references enables. It is true that with my proposal
"ref=foo cmd" where "cmd" is an external command becomes equivalent to just
"cmd" but on the other hand if cmd is a user defined function, then it
allows to call it with an updated reference, which can be very useful.

Could we reconcile both approaches? Actually, yes. The current
implementation forbids the flagging of references for export. This could be
changed. It would raise the question of what should be exported for a
reference "ref". I see only two possibilities that make sense: either
"$ref" or "${(!)ref}". The former is technically difficult to implement
efficiently while the latter is trivial. It's also what Bash does:

% bash -c 'var=foo; typeset -n -x ref=var; printenv ref'
var

If we adopt the latter, then "ref=val cmd" behaves as you suggest; the
environment will be augmented with "ref"->"val". Although, only if "val" is
a valid identifier. Otherwise, an error will be triggered. However, that's
not very different from "int=foo cmd" where "int" is an integer parameter,
which augments the environment with "int"->"0" rather than with
"ref"->"foo":

% zsh -c 'typeset -i int; int=foo printenv int'
0

Note that ksh's inline assignments behave exactly as the combined approach
described above:

% ksh -c 'function f { var=foo; typeset -n ref=var; ref=bar printenv ref;
}; f'
bar
% ksh -c 'function f { var=foo; typeset -n ref=var; ref=not-an-id printenv
ref; }; f'
ksh: f[1]: printenv: not-an-id: invalid variable name

However, I wonder whether the exporting is a bug rather than a feature
because ksh forbids explicit exports of references:

% ksh -c 'function f { var=foo; typeset -n -x ref=var; }; f'
ksh: f[1]: typeset: -n cannot be used with other options except -g

Wdyt? Could we allow flagging references for export such that "typeset -n
-x ref=var" exports "ref"->"var" and change "ref=var cmd" to A) flag "ref"
with -x and B) assign "ref" (rather than the variable referred to by "ref",
if any) with "var" ? Obviously, if we do that, then workers/54943
<http://zsh.org/workers/54943> should not be committed.

Philippe