-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Exported global vs universal variables and exec don't play well together #5258
Comments
I'm not quite sure what you're proposing here?
Do you want us to detect that we're about to run fish, and then remove the exported variables? That just seems like it complicates the rules (quite a bit if you want it to also work for wrapper scripts and such) for little gain, because |
I should hope not, as that would make our behavior wrong everywhere (no other session-specific values are saved). I actually use
I don't know. Perhaps there is a cleaner way? Can we detect that the parent process was Actually, taking a step back here. On startup, can't a universally exported variable just supercede any variables defined in the environment? After all, surely the point of universally setting a variable is to override its value, and doing so with the export flag means to override an exported such variable? e.g. would it make sense for the following behavior: mqudsi@Blitzkrieg /m/d/r/fish> set -gx foo "global foo"
mqudsi@Blitzkrieg /m/d/r/fish> set -Ux foo "universal foo"
set: Universal variable 'foo' is shadowed by the global variable of the same name.
mqudsi@Blitzkrieg /m/d/r/fish> exec fish
Welcome to fish, the friendly interactive shell
mqudsi@Blitzkrieg /m/d/r/fish> echo $foo
universal foo # exported universal variable won out and mqudsi@Blitzkrieg /m/d/r/fish> set -gx foo "global foo"
mqudsi@Blitzkrieg /m/d/r/fish> set -U foo "universal foo"
set: Universal variable 'foo' is shadowed by the global variable of the same name.
mqudsi@Blitzkrieg /m/d/r/fish> exec fish
Welcome to fish, the friendly interactive shell
mqudsi@Blitzkrieg /m/d/r/fish> echo $foo
global foo # exported variable won out over un-exported universal variable (regardless of whether the export was defined in fish or in a different shell/environment before fish was |
I missed the obvious here: this all applies to regular recursive invocations of |
Sure, but those are literally child processes of the current session, so the logic that they should inherit exported variables applies even more.
And that's cleaner? Even if it were possible (which I don't think it is), that's even worse than special-casing a child fish. And I believe you're vastly underestimating the effort required here. Think of wrapper-scripts and different fish binaries and symlinks and.... All those cases would have to not behave terribly.
Universal variables are very weak, all other scopes beat them, so they can't really be used to override anything.
The problem there is that in the "parent" the universal variable loses, but then suddenly in the child it wins? What I would like here is to get away from the abstract examples, from the "foo"s and "bar"s and get to the actual use. Maybe then we can figure out how to do it nicer. What did you do, and how did it in your opinion go wrong? |
But fish imports the existing I still think the user explicitly universally setting A user-defined global variable should mask a user-defined universal variable, but fish shouldn't silently mask a universal variable the user went out of their way to define. |
How do we know if a global variable is "user-defined" or not? |
Okay, this actually has an FAQ entry (http://fishshell.com/docs/current/faq.html): "Why doesn't It's indeed a common problem, and I'm open to suggestions for improvement. I don't think we can know if a global variable is "user-defined", and I don't think we should special-case a child or parent fish (in fact I don't even think that would help much - @mqudsi: In your example the broken $EDITOR could have also come from the terminal or tmux). The best I can come up with is to let universal variables take precedence over "imported" variables. I.e. at startup, just don't import variables from the environment for which a universal exists. This is almost @mqudsi's idea: "On startup, can't a universally exported variable just supercede any variables defined in the environment?", with the key difference that the export-bit doesn't matter. There are two problems with this:
I think that might actually be worth it. I consider both of those problems to be fairly small and unimportant, so that trading them for what we have now would be a net improvement. The former is only really a problem for XDG_CONFIG_HOME, because you can change other variables once you are in the fish session (i.e. from the commandline or in the script), and it's only a problem if someone decides to define that as a universal. Presumably it could be fixed by adding an option to pick the config directory. The latter is something that I believe happens very infrequently. |
I would be fine with that. @ridiculousfish? |
I currently make use of this snippet to solve what is essentially the same issue. I find performance acceptable, but making it a built in (optional? somehow?) behavior would be appreciated. # ignore parent exports in favor of universal exports
for export in (set -Ux)
set -eg (string split ' ' $export)[1]
end |
Environment variables are conceptually high-priority and it feels wrong to me to override them. They're commonly used for scoped configuration changes, including the XDG_CONFIG_HOME example. Note that with the current behavior, it's possible though awkward to manually allow the uvar to take precedence, as neersighted illustrates. But if uvars always took precedence, incoming environment variables would be unrecoverable. So I think the default should remain as it is. One possibility is to make this an option to But before we go down this path, how does this actually happen? Who was setting |
One possibility is stacking fish, and the path-var changes make that slightly worse. If I now do So a child fish will import it as |
I use nested fish quite heavily (fish inside tmux inside fish), which is why I use this snippet. It has lots of sharp edges, but I find careful use of universal exported variables fits my use case. I love the idea of a |
@ridiculousfish I'm honestly none too sure at this point what was the original cause of that, but I've pinpointed what actually bothers me about this the most, and I feel this is the case that best demonstrates the crux of the problem: ~> set -Ux foo foo
~> exec fish
~> set -Ux foo bar
~> echo $foo
foo In this case, at no point was a global variable ever (manually) defined. fish shadowed my variable declaration itself / for itself, and then gave that priority over internal fish variables.
Not necessarily. I think I am leaning heavily towards a solution adding a read-only scope |
@faho you mention this only in passing but this is actually a very important point. As it currently stands, this is a regression in fish 3.0 that we should fix before shipping, since we use |
I have a particular use-case of wishing to preserve the universal property of SSH_AUTH_SOCK variable so that the ssh-agent continues to work correctly everywhere, including across tmux and Python virtualenvs. However I also want it updated when logging in again and it changes, at which point the global SSH_AUTH_SOCK is correct and the universal SSH_AUTH_SOCK is now stale. The above doesn't work with the second of these two cases. In fact it looses the new values. I solve this by running an "uplift" function on the key SSH variables on every fish invocation. function uplift-variable-to-universal
for x in $argv
if set -q $x
set -l temp $$x
set -el $x
set -eg $x
set -eU $x
set -Ux $x $temp
end
end
end Then in my fish # Certain SSH variables are (correctly) exported to subprocesses e.g. to forward
# the ssh agent. But when that subprocess is a fish shell we want them to remain Universal
# so that when they change they change everywhere (including tmux and Python virtualenvs).
# A new ssh login may validly change them, and when this happens the fish shell will set the
# new values in Global scope.
# Both of these use-cases are handled by always running following on every new fish shell.
# It "uplifts" the nearest scope values into Universal scope only.
uplift-variable-to-universal SSH_AUTH_SOCK SSH_CLIENT SSH_CONNECTION No doubt the above can be written more cleanly or generically - I'm no fish expert. This fetches the nearest scope value, removes all scope versions, and then sets it to universal. The result is that either that we have copied the global to the universal (which was already the same), or the global was the new (in which case we have just updated it). I appreciate this is not dealing with the generic subject matter of this topic, but it does provide a specific use-case to think about and a possible approach. |
Universal exported variables (created by `set -xU`) used to show up both as universal and global variable in child instances of fish. As a result, when changing an exported universal variable, the new value would only be visible after a new login (or deleting the variable from global scope in each fish instance). Additionally, something like `set -xU EDITOR vim -g` would be imported into the global scope as a single word resulting in failures to execute $EDITOR in fish. We cannot simply give precedence to universal variables, because another process might have exported the same variable. Instead, we only skip importing a variable when it is equivalent to an exported universal variable with the same name. We compare their values after joining with spaces, hence skipping those imports does not change the environment fish passes to its children. Only the representation in fish is changed from `"vim -g"` to `vim -g`. Closes fish-shell#5258. This eliminates the issue fish-shell#5348 for universal variables.
Universal exported variables (created by `set -xU`) used to show up both as universal and global variable in child instances of fish. As a result, when changing an exported universal variable, the new value would only be visible after a new login (or deleting the variable from global scope in each fish instance). Additionally, something like `set -xU EDITOR vim -g` would be imported into the global scope as a single word resulting in failures to execute $EDITOR in fish. We cannot simply give precedence to universal variables, because another process might have exported the same variable. Instead, we only skip importing a variable when it is equivalent to an exported universal variable with the same name. We compare their values after joining with spaces, hence skipping those imports does not change the environment fish passes to its children. Only the representation in fish is changed from `"vim -g"` to `vim -g`. Closes fish-shell#5258. This eliminates the issue fish-shell#5348 for universal variables.
Universal exported variables (created by `set -xU`) used to show up both as universal and global variable in child instances of fish. As a result, when changing an exported universal variable, the new value would only be visible after a new login (or deleting the variable from global scope in each fish instance). Additionally, something like `set -xU EDITOR vim -g` would be imported into the global scope as a single word resulting in failures to execute $EDITOR in fish. We cannot simply give precedence to universal variables, because another process might have exported the same variable. Instead, we only skip importing a variable when it is equivalent to an exported universal variable with the same name. We compare their values after joining with spaces, hence skipping those imports does not change the environment fish passes to its children. Only the representation in fish is changed from `"vim -g"` to `vim -g`. Closes fish-shell#5258. This eliminates the issue fish-shell#5348 for universal variables.
Universal exported variables (created by `set -xU`) used to show up both as universal and global variable in child instances of fish. As a result, when changing an exported universal variable, the new value would only be visible after a new login (or deleting the variable from global scope in each fish instance). Additionally, something like `set -xU EDITOR vim -g` would be imported into the global scope as a single word resulting in failures to execute $EDITOR in fish. We cannot simply give precedence to universal variables, because another process might have exported the same variable. Instead, we only skip importing a variable when it is equivalent to an exported universal variable with the same name. We compare their values after joining with spaces, hence skipping those imports does not change the environment fish passes to its children. Only the representation in fish is changed from `"vim -g"` to `vim -g`. Closes #5258. This eliminates the issue #5348 for universal variables.
Prior to this change, there was a separate "universal" scope above the global scope. It was possible to have a universal variable which was shadowed by a global of the same name. This changes "universal" to be a *variable property*, like "exports." Only globals may have this property set. "Universal variable" now means "global variable that is also marked as universal." `set --universal` now implies the `--global` option. When you create, modify, or erase a universal global, all other sessions change their corresponding global as well. The most important implication is that *universal variables now take precedence over inherited environment variables*. This addresses fish-shell#5258 and fish-shell#5348. In principle this allows variables like PATH to be universal; however that's not recommended (yet). For compatibility, we retain `set --query --universal` which now means "is there a global variable with the universal property." One important implication is that `set -qg` will now succeed for universal variables: > set --universal myvar someval > set --query --global myvar # succeeds Functions which expected this to fail (`abbr` is an example) need to be updated. Fixes fish-shell#5348. Fixes fish-shell#5258. Fixes fish-shell#7379. Fixes fish-shell#7317.
Prior to this change, there was a separate "universal" scope above the global scope. It was possible to have a universal variable which was shadowed by a global of the same name. This changes "universal" to be a *variable property*, like "exports." Only globals may have this property set. "Universal variable" now means "global variable that is also marked as universal." `set --universal` now implies the `--global` option. When you create, modify, or erase a universal global, all other sessions change their corresponding global as well. The most important implication is that *universal variables now take precedence over inherited environment variables*. This addresses fish-shell#5258 and fish-shell#5348. In principle this allows variables like PATH to be universal; however that's not recommended (yet). For compatibility, we retain `set --query --universal` which now means "is there a global variable with the universal property." One important implication is that `set -qg` will now succeed for universal variables: > set --universal myvar someval > set --query --global myvar # succeeds Functions which expected this to fail (`abbr` is an example) need to be updated. Fixes fish-shell#5348. Fixes fish-shell#5258. Fixes fish-shell#7379. Fixes fish-shell#7317.
Warning: This one is messy and I'm not 100% on what the correct behavior is.
When fish is executed exports are imported as global variables. As a result, exported global variables end up outliving their scope, which is defined as being the current session. When
exec fish
is called, the environment block is persisted (which is by design and a correct implementation ofexec
to the best of my knowledge), and the global variable carries into the new fish session.Because of shadowing rules, the scope of a global variable also means global variables "win over" universal variables. Which is fine within a session, regardless of whether values are exported or not:
If you start a new fish shell in a separate terminal right now and execute
echo $FOO
, the value will beuniversal foo
.But back in this same terminal/session, execute
exec fish
instead, then runecho $FOO
and the result will beglobal foo
and notuniversal foo
.This is fish's env design meets variable scopes, and it's not clear who the winner should be. Personally, I think exported variables are for interacting with the outside world and when it's fish-to-fish communication (within a school of fish, if I may), fish's architecture should win out. The scope of global variables is clearly defined as ending when the session is over, and
exec fish
definitely starts a new session (if the variable weren't exported, it wouldn't have been preserved), so the newly-instantiated universal variable should win out.But at the same time, if the situation were the following (after having declared `set -Ux FOO "universal foo"):
Starting from sh:
I think it does make sense for
global foo
to win out since there's no other way of telling fish to start up with that variable assigned to that value, effectively overriding the universal binding with a (temporary) global one.(Of course that means that there's no "automatic" way of overriding the default value of an environment variable set by the environment prior to invocation of fish without explicitly doing it in
config.fish
so that it's reset each time fish starts up.)The text was updated successfully, but these errors were encountered: