Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

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



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(<assignments>)
    - Execute <command>
- Otherwise, if <command> is a function or a builtin:
  - Call save_params(<assignments>)
  - Call addvars(<assignments>)
  - Execute <command>
  - Call restore_params(<assignments>)

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

Implementation of save_params(<assignments>):
- For each <assignment> in <assignments>
  - Make a copy of the parameter assigned by <assignment>

Implementation of restore_params(<assignments>):
- 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



Messages sorted by: Reverse Date, Date, Thread, Author