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

Philippe Altherr <[email protected]> Tue, 14 Jul 2026 21:26:30 +0200
Newsgroups gmane.comp.shells.zsh.devel
Message-ID <CAGdYchv7VsU9xLJHhB=6s3AdHs2DLhZJs1zZoSr9Ukga256Xxg@mail.gmail.com>
--000000000000c7d5800656972f61
Content-Type: text/plain; charset="UTF-8"

Below is a description of how inline assignments are implemented:

Implementation of "<assignments> <command>":
- If <command> is an external command:
  - In a new subshell run the following:
    - Call addvars
<https://github.com/zsh-users/zsh/blob/0cd1b3881ef01b990fbfed3d11a8e0ecbbcafd3c/Src/exec.c#L2556>
(<assignments>)
    - Execute <command>
- Otherwise, if <command> is a function or a builtin:
  - Call save_params
<https://github.com/zsh-users/zsh/blob/0cd1b3881ef01b990fbfed3d11a8e0ecbbcafd3c/Src/exec.c#L4467>
(<assignments>)
  - Call addvars
<https://github.com/zsh-users/zsh/blob/0cd1b3881ef01b990fbfed3d11a8e0ecbbcafd3c/Src/exec.c#L2556>
(<assignments>)
  - Execute <command>
  - Call restore_params
<https://github.com/zsh-users/zsh/blob/0cd1b3881ef01b990fbfed3d11a8e0ecbbcafd3c/Src/exec.c#L4521>
(<assignments>)

Implementation of addvars
<https://github.com/zsh-users/zsh/blob/0cd1b3881ef01b990fbfed3d11a8e0ecbbcafd3c/Src/exec.c#L2556>
(<assignments>):
- For each <assignment> in <assignments>
  - If the <assignment>'s lhs contains a subscript or if the assignment>'s rhs
is an array value:
    - Execute <assignment>
  - Otherwise:
    - Save the state of the option ALL_EXPORT
    - Enable the option ALL_EXPORT
    - Execute <assignment>
    - Restore the saved state of the option ALL_EXPORT

Implementation of save_params
<https://github.com/zsh-users/zsh/blob/0cd1b3881ef01b990fbfed3d11a8e0ecbbcafd3c/Src/exec.c#L4467>
(<assignments>):
- For each <assignment> in <assignments>
  - Make a copy of the parameter assigned by <assignment>

Implementation of restore_params
<https://github.com/zsh-users/zsh/blob/0cd1b3881ef01b990fbfed3d11a8e0ecbbcafd3c/Src/exec.c#L4521>
(<assignments>):
- For each <assignment> in <assignments>
  - Use the copy made by save_params
<https://github.com/zsh-users/zsh/blob/0cd1b3881ef01b990fbfed3d11a8e0ecbbcafd3c/Src/exec.c#L4467>
to
restore the parameter assigned by <assignment>


A few important takeaways:

   - Inline assignments are executed like normal ones. The code that
   implements them has no way to distinguish between inline and regular
   assignments.


   - Inline assignments aren't explicitly exported. The implementation
   piggybacks on the option ALL_EXPORT to trigger the -x flagging of the
   assigned parameters. The command, if it is an external one, is run in an
   environment that contains the name value pairs of all the -x flagged
   parameters.


   - The implementation of inline assignments never looks at the type of
   the assigned parameters; all types of parameters are handled in the same
   way.


> % bash -c 'var=foo; typeset -n -x ref=var; printenv ref'
> > var
>
> That possibility isn't particularly useful nor likely to match what
> a user would expect/want. Better to leave the current implementation
> (exported namerefs forbidden).
>

I agree that it doesn't look particularly useful. I'm fine with not
allowing -x on references but that also implies that "ref=foo cmd" won't be
able to export a value named "ref" because only parameters flagged with -x
get exported or that the implementation of inline assignments needs some
fundamental changes specifically tailored for references. More on this
subject below.

> 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
> I don't get that error. Looks like a bug, perhaps later
> introduced/fixed.
>

I have the following version:

% ksh --version
  version         sh (AT&T Research) 93u+m/1.0.10 2024-08-01

I don't think this error is a bug. Quite on the contrary, I think that this
error is necessary. Consider the following example:

% ksh -c 'function g { typeset -p var ref; }; function f { var=foo; typeset
-n ref=var; ref=bar g; }; f'
var=foo
typeset -n -x ref=bar

If you replace "bar" with "not-an-id" and there was no error, it would
create a reference initialized with an invalid variable name.


> In my opinion, an assignment before a command should be the same as a
> normal assignment as far as possible with the addition of adding the
> export of the explicitly named variable.
>

In principle, I agree with these requirements however I think that there is
an even more imperative requirement, namely that "cmd" must run in exactly
the same environment when run with "ref=foo cmd" as when run with "fun() {
cmd }; ref=foo fun". This requirement puts severe constraints on what's
possible. If you consider only the case "ref=foo cmd", then you could
modify the implementation of inline assignments for example to augment the
environment with the pair "ref"->"foo" just after the call to addvars and
before the execution of "cmd". However, nothing similar is possible for the
case "fun() { cmd }; ref=foo fun". Here, your only levers are to modify the
parameter table that will be visible to "fun" in such a way that when "fun"
executes "cmd", the environment will be the desired one.

For our discussion we should consider the following example:

var=foo
typeset -n ref=var
fun() {
  typeset -p var ref
  printenv var
  printenv ref
}
ref=bar fun

What we need to answer is what should the "typeset -p" and the two
"printenv" print. What the implementation of "ref=bar fun" can do is change
the value of ref and/or var and modify their flags. We can also decide
whether -x is allowed on references and what it has for effect. We could
possibly also define one or more new parameter flags (although there are
only very few PM bits left).

So:
> var=foo;        typeset -n ref=var;    ref=bar env - export ref=bar
> var=foo;        typeset -n -x ref=var; ref=bar env - export ref=var
> export var=foo; typeset -n ref=var;    ref=bar env - export ref=bar var=bar
>

(I assume that the last column shows what you expect to be in the
environment of the "env" command.)

If we agree that "ref=foo cmd" and "fun() { cmd }; ref=foo fun" should
produce the same environment for "cmd", then I don't think that the above
is possible. Could you show for each of your 3 example what should be
printed if "env" is replaced with the function "fun" defined above?

A normal assignment to a reference affects the target variable. Only
> with -n options to typeset/unset etc would it be apparent that you
> have a reference. But it is most intuitive if the extra export for
> assignments before a command apply to what is explicitly named so
>
> *I would only export var in the latter case where it is already marked for
> export* (and it's value should be restored afterwards).


I agree with that, I find the current behavior where "ref=bar env" exports
"var" rather unexpected.

I would export
> ref in all three cases because it has been named.
>

Exporting "ref" is possible but I don't think we can export it (and "var")
with the values that you suggest above.

We allow a nameref to be created without a target and the first
> assignment then sets the reference so, e.g:
> typeset -n ref; var=foo; ref=var env - *export ref=foo*
> I'd want that to restore ref pointing nowhere after the command ends.
>

I don't understand why here the exported value of "ref" should be "foo"
(and not "var") while in your first example above the value is "bar".

---

My proposal changes the implementation described above as follows:

   - save_params and restore_params save and restore the parameter named in
   the inline assignment rather than the one referred to by that parameter.


   - The two "Execute <assignment>" in addvars run with a flag that tells
   the implementation of assignments to assign the value to the named
   parameter rather than to the parameter referred to by that parameter.

Philippe

--000000000000c7d5800656972f61
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

<div dir=3D"ltr"><div dir=3D"ltr">Below is a description of how inline assi=
gnments are implemented:<div><br></div><div>Implementation of=C2=A0&quot;&l=
t;assignments&gt; &lt;command&gt;&quot;:</div><div>-=C2=A0If &lt;command&gt=
; is an external command:</div><div>=C2=A0 - In a new subshell run the foll=
owing:</div><div>=C2=A0 =C2=A0 - Call <a href=3D"https://github.com/zsh-use=
rs/zsh/blob/0cd1b3881ef01b990fbfed3d11a8e0ecbbcafd3c/Src/exec.c#L2556">addv=
ars</a>(&lt;assignments&gt;)</div><div>=C2=A0 =C2=A0 - Execute &lt;command&=
gt;</div><div>- Otherwise, if &lt;command&gt; is a function or a builtin:</=
div><div>=C2=A0 - Call <a href=3D"https://github.com/zsh-users/zsh/blob/0cd=
1b3881ef01b990fbfed3d11a8e0ecbbcafd3c/Src/exec.c#L4467">save_params</a>(&lt=
;assignments&gt;)</div><div>=C2=A0=C2=A0<span style=3D"background-color:tra=
nsparent">- Call</span><span style=3D"background-color:transparent">=C2=A0<=
/span><a href=3D"https://github.com/zsh-users/zsh/blob/0cd1b3881ef01b990fbf=
ed3d11a8e0ecbbcafd3c/Src/exec.c#L2556" style=3D"background-color:transparen=
t">addvars</a><span style=3D"background-color:transparent">(&lt;assignments=
&gt;)</span></div><div><span style=3D"background-color:transparent">=C2=A0 =
- Execute &lt;command&gt;</span></div><div><span style=3D"background-color:=
transparent">=C2=A0 - Call <a href=3D"https://github.com/zsh-users/zsh/blob=
/0cd1b3881ef01b990fbfed3d11a8e0ecbbcafd3c/Src/exec.c#L4521">restore_params<=
/a>(</span><span style=3D"background-color:transparent">&lt;assignments&gt;=
)</span></div><div><span style=3D"background-color:transparent"><br></span>=
</div><div>Implementation of=C2=A0<a href=3D"https://github.com/zsh-users/z=
sh/blob/0cd1b3881ef01b990fbfed3d11a8e0ecbbcafd3c/Src/exec.c#L2556">addvars<=
/a>(&lt;assignments&gt;):<span style=3D"background-color:transparent"></spa=
n></div><div>- For each &lt;assignment&gt; in=C2=A0&lt;assignments&gt;</div=
><div>=C2=A0 - If the=C2=A0<span style=3D"background-color:transparent">&lt=
;assignment&gt;&#39;s lhs contains a subscript or if the=C2=A0</span>assign=
ment&gt;&#39;s<span style=3D"background-color:transparent">=C2=A0rhs is an =
array value:</span></div><div><span style=3D"background-color:transparent">=
=C2=A0 =C2=A0 - Execute=C2=A0</span><span style=3D"background-color:transpa=
rent">&lt;assignment&gt;</span></div><div><span style=3D"background-color:t=
ransparent">=C2=A0 - Otherwise:</span></div><div><span style=3D"background-=
color:transparent">=C2=A0 =C2=A0 - Save the state of the option=C2=A0</span=
><span style=3D"background-color:transparent">ALL_EXPORT</span></div><div><=
span style=3D"background-color:transparent">=C2=A0 =C2=A0 - Enable the opti=
on ALL_EXPORT</span></div><div><div><span style=3D"background-color:transpa=
rent">=C2=A0 =C2=A0 - Execute=C2=A0</span><span style=3D"background-color:t=
ransparent">&lt;assignment&gt;</span></div><div><span style=3D"background-c=
olor:transparent">=C2=A0 =C2=A0 - Restore the saved state of=C2=A0</span><s=
pan style=3D"background-color:transparent">the option=C2=A0</span><span sty=
le=3D"background-color:transparent">ALL_EXPORT</span></div><br class=3D"gma=
il-Apple-interchange-newline"></div><div>Implementation of=C2=A0<a href=3D"=
https://github.com/zsh-users/zsh/blob/0cd1b3881ef01b990fbfed3d11a8e0ecbbcaf=
d3c/Src/exec.c#L4467">save_params</a>(&lt;assignments&gt;):</div><div>- For=
 each &lt;assignment&gt; in=C2=A0&lt;assignments&gt;</div><div>=C2=A0 - Mak=
e a copy of the parameter assigned by=C2=A0<span style=3D"background-color:=
transparent">&lt;assignment&gt;</span></div><div><span style=3D"background-=
color:transparent"><br></span></div><div><div>Implementation of=C2=A0<a hre=
f=3D"https://github.com/zsh-users/zsh/blob/0cd1b3881ef01b990fbfed3d11a8e0ec=
bbcafd3c/Src/exec.c#L4521">restore_params</a>(&lt;assignments&gt;):</div><d=
iv>- For each &lt;assignment&gt; in=C2=A0&lt;assignments&gt;</div><div>=C2=
=A0 - Use the copy made by=C2=A0<a href=3D"https://github.com/zsh-users/zsh=
/blob/0cd1b3881ef01b990fbfed3d11a8e0ecbbcafd3c/Src/exec.c#L4467">save_param=
s</a>=C2=A0to restore the parameter assigned by=C2=A0<span style=3D"backgro=
und-color:transparent">&lt;assignment&gt;</span></div></div><div><span styl=
e=3D"background-color:transparent"><br></span></div><div><span style=3D"bac=
kground-color:transparent"><br></span></div><div><span style=3D"background-=
color:transparent">A few important takeaways:</span></div><div><ul><li><spa=
n style=3D"background-color:transparent">Inline assignments are executed li=
ke normal ones. The code that implements them has no way to distinguish=C2=
=A0between inline and regular assignments.</span></li></ul><ul><li>Inline a=
ssignments aren&#39;t explicitly=C2=A0exported. The implementation piggybac=
ks on the=C2=A0option ALL_EXPORT to trigger the -x flagging of the assigned=
 parameters. The command, if it is an external one, is run in an environmen=
t that contains the name value pairs of all the -x flagged parameters.</li>=
</ul><ul><li>The implementation of inline assignments never looks at the ty=
pe of the assigned parameters; all types of parameters are handled in the s=
ame way.</li></ul></div><div><br></div></div><div class=3D"gmail_quote gmai=
l_quote_container"><blockquote class=3D"gmail_quote" style=3D"margin:0px 0p=
x 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">&gt; %=
 bash -c &#39;var=3Dfoo; typeset -n -x ref=3Dvar; printenv ref&#39;<br>
&gt; var<br>
<br>
That possibility isn&#39;t particularly useful nor likely to match what<br>
a user would expect/want. Better to leave the current implementation<br>
(exported namerefs forbidden).<br></blockquote><div><br></div><div>I agree =
that it doesn&#39;t look=C2=A0particularly useful. I&#39;m fine with not al=
lowing -x on references but that also implies that &quot;ref=3Dfoo cmd&quot=
; won&#39;t be able to export a value named &quot;ref&quot; because only pa=
rameters flagged with -x get exported or that the implementation of=C2=A0in=
line assignments needs some fundamental changes specifically tailored for r=
eferences. More on this subject below.</div><div><br></div><blockquote clas=
s=3D"gmail_quote" style=3D"margin:0px 0px 0px 0.8ex;border-left:1px solid r=
gb(204,204,204);padding-left:1ex">
&gt; Note that ksh&#39;s=C2=A0inline=C2=A0assignments behave exactly as the=
 combined approach<br>
&gt; described above:<br>
&gt;<br>
&gt; % ksh -c &#39;function f { var=3Dfoo; typeset -n ref=3Dvar; ref=3Dbar =
printenv ref; }; f&#39;<br>
&gt; bar<br>
&gt; % ksh -c &#39;function f { var=3Dfoo; typeset -n ref=3Dvar; ref=3Dnot-=
an-id printenv ref;<br>
&gt; }; f&#39;<br>
&gt; ksh: f[1]: printenv: not-an-id: invalid variable name<br>
I don&#39;t get that error. Looks like a bug, perhaps later<br>
introduced/fixed.<br></blockquote><div><br></div><div>I have the following =
version:</div><div><br></div><div><font face=3D"monospace">% ksh --version<=
br>=C2=A0 version =C2=A0 =C2=A0 =C2=A0 =C2=A0 sh (AT&amp;T Research) 93u+m/=
1.0.10 2024-08-01</font></div><div><br></div><div>I don&#39;t think this er=
ror is a bug. Quite on the contrary, I think that this error is necessary. =
Consider the following example:</div><div><br></div><div><font face=3D"mono=
space">% ksh -c &#39;function g { typeset -p var ref; }; function f { var=
=3Dfoo; typeset -n ref=3Dvar; ref=3Dbar g; }; f&#39; =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 =C2=A0 <br>var=3Dfoo<br>typeset -n -x ref=3Dbar</font><br></div><div=
><br></div><div>If you replace &quot;bar&quot; with &quot;not-an-id&quot; a=
nd there was no error, it would create a reference initialized with an inva=
lid=C2=A0variable name.</div><div>=C2=A0</div><blockquote class=3D"gmail_qu=
ote" style=3D"margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,20=
4);padding-left:1ex">In my opinion, an assignment before a command should b=
e the same as a<br>
normal assignment as far as possible with the addition of adding the<br>
export of the explicitly named variable.<br></blockquote><div><br></div><di=
v>In principle, I agree with these requirements however I think that there =
is an even more imperative requirement, namely that=C2=A0<span style=3D"bac=
kground-color:transparent">&quot;cmd&quot; must run in exactly the same env=
ironment when run with &quot;ref=3Dfoo cmd&quot; as when run with &quot;fun=
() { cmd }; ref=3Dfoo fun&quot;. This requirement puts severe constraints o=
n what&#39;s possible. If you consider only the case=C2=A0</span><span styl=
e=3D"background-color:transparent">&quot;ref=3Dfoo cmd&quot;, then you coul=
d modify the implementation of inline assignments for example to augment th=
e environment with the pair &quot;ref&quot;-&gt;&quot;foo&quot; just after =
the call to addvars and before the execution of &quot;cmd&quot;. However, n=
othing similar is possible for the case=C2=A0</span><span style=3D"backgrou=
nd-color:transparent">&quot;fun() { cmd }; ref=3Dfoo fun&quot;. Here, your =
only levers are to modify the parameter table that will be visible to &quot=
;fun&quot; in such a way that when &quot;fun&quot; executes &quot;cmd&quot;=
, the environment will be the desired one.</span></div><div><span style=3D"=
background-color:transparent"><br></span></div><div><span style=3D"backgrou=
nd-color:transparent">For our discussion we should consider the following e=
xample:</span></div><div><span style=3D"background-color:transparent"><br><=
/span></div><div><span style=3D"background-color:transparent">var=3Dfoo</sp=
an></div><div><span style=3D"background-color:transparent">typeset -n ref=
=3Dvar</span></div><div><span style=3D"background-color:transparent">fun() =
{</span></div><div><span style=3D"background-color:transparent">=C2=A0 type=
set -p var ref</span></div><div><span style=3D"background-color:transparent=
">=C2=A0 printenv var</span></div><div><span style=3D"background-color:tran=
sparent">=C2=A0 printenv ref</span></div><div><span style=3D"background-col=
or:transparent">}</span></div><div><span style=3D"background-color:transpar=
ent">ref=3Dbar fun</span></div><div><span style=3D"background-color:transpa=
rent"><br></span></div><div><span style=3D"background-color:transparent">Wh=
at we need to answer is what should the &quot;typeset -p&quot; and the two =
&quot;printenv&quot; print. What the implementation of &quot;</span><span s=
tyle=3D"background-color:transparent">ref=3Dbar fun&quot; can do is change =
the value of ref and/or var and modify their flags. We can also decide whet=
her -x is allowed on references and what it has for effect. We could possib=
ly also define one or more new parameter flags (although there are only ver=
y few PM bits left).</span></div><div><br></div><blockquote class=3D"gmail_=
quote" style=3D"margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,=
204);padding-left:1ex">
So:<br>
var=3Dfoo;=C2=A0 =C2=A0 =C2=A0 =C2=A0 typeset -n ref=3Dvar;=C2=A0 =C2=A0 re=
f=3Dbar env - export ref=3Dbar<br>
var=3Dfoo;=C2=A0 =C2=A0 =C2=A0 =C2=A0 typeset -n -x ref=3Dvar; ref=3Dbar en=
v - export ref=3Dvar<br>
export var=3Dfoo; typeset -n ref=3Dvar;=C2=A0 =C2=A0 ref=3Dbar env - export=
 ref=3Dbar var=3Dbar<br></blockquote><div><br></div><div>(I assume that the=
 last column shows what you expect to be in the environment of the &quot;en=
v&quot; command.)</div><div><br></div><div>If we agree that=C2=A0<span styl=
e=3D"background-color:transparent">&quot;ref=3Dfoo cmd&quot; and=C2=A0</spa=
n><span style=3D"background-color:transparent">&quot;fun() { cmd }; ref=3Df=
oo fun&quot; should produce the same=C2=A0</span><span style=3D"background-=
color:transparent">environment for &quot;cmd&quot;, then I don&#39;t think =
that the above is possible. Could you show for each of your 3 example what =
should be printed if &quot;env&quot; is replaced with the function &quot;fu=
n&quot; defined above?</span></div><div><br></div><blockquote class=3D"gmai=
l_quote" style=3D"margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,20=
4,204);padding-left:1ex">
A normal assignment to a reference affects the target variable. Only<br>
with -n options to typeset/unset etc would it be apparent that you<br>
have a reference. But it is most intuitive if the extra export for<br>
assignments before a command apply to what is explicitly named so <b>I<br>
would only export var in the latter case where it is already marked for<br>
export</b> (and it&#39;s value should be restored afterwards).</blockquote>=
<div><br></div><div>I agree with that, I find the current behavior where &q=
uot;ref=3Dbar env&quot; exports &quot;var&quot; rather unexpected.</div><di=
v><br></div><blockquote class=3D"gmail_quote" style=3D"margin:0px 0px 0px 0=
.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"> I would expo=
rt<br>
ref in all three cases because it has been named.<br></blockquote><div><br>=
</div><div>Exporting &quot;ref&quot; is possible but I don&#39;t think we c=
an export it (and &quot;var&quot;) with the values that you suggest above.<=
/div><div><br></div><blockquote class=3D"gmail_quote" style=3D"margin:0px 0=
px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
We allow a nameref to be created without a target and the first<br>
assignment then sets the reference so, e.g:<br>
typeset -n ref; var=3Dfoo; ref=3Dvar env - <b>export ref=3Dfoo</b><br>
I&#39;d want that to restore ref pointing nowhere after the command ends.<b=
r></blockquote><div><br></div><div>I don&#39;t understand why here the expo=
rted value of &quot;ref&quot; should be &quot;foo&quot; (and not &quot;var&=
quot;) while in your first example above the value is &quot;bar&quot;.</div=
><div><br></div><div>---</div><div><br></div><div>My proposal changes the i=
mplementation described above as follows:</div><div><ul><li>save_params and=
=C2=A0restore_params save and restore the parameter named in the inline ass=
ignment rather than the one referred to by that parameter.</li></ul><ul><li=
>The two &quot;Execute &lt;assignment&gt;&quot; in=C2=A0addvars run with a =
flag that tells the implementation of assignments to assign the value to th=
e named parameter rather than to the parameter referred to by that paramete=
r.</li></ul></div><div>Philippe</div><div><br></div></div></div>

--000000000000c7d5800656972f61--