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"&l=
t;assignments> <command>":</div><div>-=C2=A0If <command>=
; 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>(<assignments>)</div><div>=C2=A0 =C2=A0 - Execute <command&=
gt;</div><div>- Otherwise, if <command> 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>(<=
;assignments>)</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">(<assignments=
>)</span></div><div><span style=3D"background-color:transparent">=C2=A0 =
- Execute <command></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"><assignments>=
)</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>(<assignments>):<span style=3D"background-color:transparent"></spa=
n></div><div>- For each <assignment> in=C2=A0<assignments></div=
><div>=C2=A0 - If the=C2=A0<span style=3D"background-color:transparent"><=
;assignment>'s lhs contains a subscript or if the=C2=A0</span>assign=
ment>'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"><assignment></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"><assignment></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>(<assignments>):</div><div>- For=
each <assignment> in=C2=A0<assignments></div><div>=C2=A0 - Mak=
e a copy of the parameter assigned by=C2=A0<span style=3D"background-color:=
transparent"><assignment></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>(<assignments>):</div><d=
iv>- For each <assignment> in=C2=A0<assignments></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"><assignment></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'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">> %=
bash -c 'var=3Dfoo; typeset -n -x ref=3Dvar; printenv ref'<br>
> var<br>
<br>
That possibility isn'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't look=C2=A0particularly useful. I'm fine with not al=
lowing -x on references but that also implies that "ref=3Dfoo cmd"=
; won't be able to export a value named "ref" 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">
> Note that ksh's=C2=A0inline=C2=A0assignments behave exactly as the=
combined approach<br>
> described above:<br>
><br>
> % ksh -c 'function f { var=3Dfoo; typeset -n ref=3Dvar; ref=3Dbar =
printenv ref; }; f'<br>
> bar<br>
> % ksh -c 'function f { var=3Dfoo; typeset -n ref=3Dvar; ref=3Dnot-=
an-id printenv ref;<br>
> }; f'<br>
> ksh: f[1]: printenv: not-an-id: invalid variable name<br>
I don'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&T Research) 93u+m/=
1.0.10 2024-08-01</font></div><div><br></div><div>I don'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 'function g { typeset -p var ref; }; function f { var=
=3Dfoo; typeset -n ref=3Dvar; ref=3Dbar g; }; f' =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 "bar" with "not-an-id" 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">"cmd" must run in exactly the same env=
ironment when run with "ref=3Dfoo cmd" as when run with "fun=
() { cmd }; ref=3Dfoo fun". This requirement puts severe constraints o=
n what's possible. If you consider only the case=C2=A0</span><span styl=
e=3D"background-color:transparent">"ref=3Dfoo cmd", then you coul=
d modify the implementation of inline assignments for example to augment th=
e environment with the pair "ref"->"foo" just after =
the call to addvars and before the execution of "cmd". However, n=
othing similar is possible for the case=C2=A0</span><span style=3D"backgrou=
nd-color:transparent">"fun() { cmd }; ref=3Dfoo 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.</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 "typeset -p" and the two =
"printenv" print. What the implementation of "</span><span s=
tyle=3D"background-color:transparent">ref=3Dbar fun" 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 "en=
v" command.)</div><div><br></div><div>If we agree that=C2=A0<span styl=
e=3D"background-color:transparent">"ref=3Dfoo cmd" and=C2=A0</spa=
n><span style=3D"background-color:transparent">"fun() { cmd }; ref=3Df=
oo fun" should produce the same=C2=A0</span><span style=3D"background-=
color:transparent">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 "fu=
n" 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'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" exports "var" 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 "ref" is possible but I don't think we c=
an export it (and "var") 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'd want that to restore ref pointing nowhere after the command ends.<b=
r></blockquote><div><br></div><div>I don't understand why here the expo=
rted value of "ref" should be "foo" (and not "var&=
quot;) while in your first example above the value is "bar".</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 "Execute <assignment>" 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--