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:
- Execute <command>
- Otherwise, if <command> is a function or a builtin:
- Execute <command>
Implementation of
addvars(<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
- For each <assignment> in <assignments>
- Make a copy of the parameter assigned by <assignment>
- For each <assignment> in <assignments>
- Use the copy made by
save_params 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