PowerShell should support inheritance of default shell environment variables as a default shell

Created on 14 May 2016  路  25Comments  路  Source: PowerShell/PowerShell

Steps to reproduce

I tried this on OS X

  • Set powershell as your default shell (so it doesn't inherit your default shell env variables)
  • Run
> $env:PATH
/usr/bin:/bin

It doesn't have /usr/local/bin in PATH
So I cannot run powershell (and gitk, which really was my motivation)

Expected behavior

/usr/local/bin should be there
powershell should be able to run apps from it, i.e. gitk, powershell

Actual behavior

Cannot run powershell

Environment data

v0.3.0

Committee-Reviewed Issue-Bug OS-Linux OS-macOS Resolution-Fixed Usability

Most helpful comment

Thanks @mklement0 - you da man!

All 25 comments

OS X sets up default paths for shells by calling out to path_helper. Bash (and Zsh, and Csh) all execute

eval `/usr/libexec/path_helper -s`

in their respective /etc/profile files. The OS Xy way for PowerShell to do this would be to add a similar call to our system profile.

However, path_helper was of course not designed with PowerShell in mind, and so it only can emit the PATH and MANPATH variables in two styles, C-shell and Bourne shell. We don't really have much of an option but to parse its output and reinterpret it for PowerShell.

It's easy: string PATH = MANPATH.Replace(':', ';'); :)

Also, it screws up Start-PSBootstrap (on OS X), because it uses installer

vors@Sergeis-MacBook ~/d/fzf> whereis installer
/usr/sbin/installer

Meanwhile, here is a one-liner that you can add to the profile to fix it

$path_helper = /usr/libexec/path_helper; if ($path_helper -match '"(.*)"') { $env:PATH = $Matches[1] }

@vors what about MANPATH?

@andschwa ah, what's with it?

I think this is blocked on PowerShell/PowerShell-RFC#92. Either way, we need to fix it for 6.0.0.

This is no longer blocked on that above RFC: our approach should create a system-level profile on each distro and macOS that runs the PowerShell equivalent of whatever other default shells call in order to build the "login shell" set of environment variables and state.

In other words, we need to have what @vors put above in the default macOS profile.

However, this will break when PowerShell isn't a login shell because we run profiles by default, and the profile would overwrite any environment variables set in a parent bash (or other shell).

@PowerShell/powershell-committee agrees that PowerShell needs to act like other shells, need to understand impact of specifics

@SteveL-MSFT and I think this might just be as simple as invoking /etc/profile when in a login shell scenario and to copy the environment variables that get generated back into PowerShell.

Would love some domain-expertise from @mklement0 if you're around. 馃槃

Context here: https://unix.stackexchange.com/questions/38175/difference-between-login-shell-and-non-login-shell

@joeyaiello:

Happy to chime in, but to be clear about what I can contribute: I know macOS pretty well; my go-to Linux platform for contrasting macOS with Linux is Ubuntu; however, I don't have a good sense of what's standard / typical across _all_ major Linux distros.

  • The issue at hand is not macOS-specific; Fedora (and, I presume, RedHat) also adds directories to $PATH in /etc/profile- despite that fact that Fedora and Ubuntu (others?) have a separate, shell-independent file for defining system-wide environment variables, /etc/environment.
    On Ubuntu, it is the latter that makes $PATH additions.
    _Update: This is likely not a problem in LOCAL sessions, but would be with ssh - see below_.

  • Login shells - if they're POSIX-compatible - _de facto_ source /etc/profile _and_ $HOME/.profile - though I couldn't find evidence of that as a _requirement_ in the POSIX spec. (did I miss it?) - $HOME/.profile is mentioned as "typically executed" in http://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xcu_chap02.html, but there's no mention of /etc/profile; bash allows preempting $HOME/.profile (but not /etc/profile) with Bash-specific alternatives ($HOME/.bash_profile (highest precedence) or (rarely used) $HOME/.bash_login); an overview of the behavior of the major POSIX-like shells (and csh variants, which I don't think we need to worry about) can be found at https://en.wikipedia.org/wiki/Unix_shell#Configuration_files

  • What POSIX does mandate is support for the - rarely used - $ENV variable "if the system supports the User Portability Utilities option" (whatever that means), which may point to a script to be sourced for _any_ interactive shell - whether it is a login shell or not - see http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_05_03.
    While this is the closest equivalent to PowerShell's $PROFILE (given that PowerShell has no concept of login vs. non-login shells), I've never seen it used in practice, and bash only honors it when it explicitly run in POSIX-compatibility mode.

  • On Linux, the default shell is called as a _login_ shell _once_, when the user initially logs on to the OS. Seemingly - at least on Ubuntu - a /bin/sh login shell is always used when a user logs in to the OS, which does process /etc/profile, and seemingly _all_ user processes (not just shells, and irrespective of whether or not they're POSIX-like shells) then _inherit process-level attributes_ from it, which notably includes environment variables , umask and ulimit values; others?

    • However, system processes - such as the ssh server component - seemingly do _not_ inherit from this sh process, which means that remotely accessing a Linux machine with ssh sees the definitions from /etc/profile only if the remotely started shell instance itself processes it - which is why ssh starts the shells used as a _login_ shell.
  • On macOS, _every_ shell instance created via Terminal.app, the default terminal (and also third-party terminals such as iTerm.app) is a _login_ shell.


The fundamental question is to what extent PowerShell needs to play by these rules.

  • /etc/profile is the most important file, because it contains important environment definitions on macOS, and, as stated, on at least 1 major Linux distro.

    • On Linux - at least on Ubuntu - that is not a concern for _local_ sessions, for the reasons explained above (all processes inherit the env. variables from /etc/profile, which is always processed when the user logs on to the OS, and also from /etc/environment).

      • It is, however, a concern for ssh sessions, whose server component itself does not process /etc/profile, so that shells created remotely via ssh must process /etc/profile themselves, which POSIX-like shells do, because ssh creates _login_ shells, by way of prefixing the invocation name ($0) with -, which the shells are expected to honor.
    • On macOS, the ssh problem applies too, but even local sessions are expected to process /etc/profile, because local shell processes on macOS do _not_ inherit from a single sh process that processes /etc/profile; instead, the terminal programs on macOS create _login_ shells _by default_, every time a shell is instantiated, so if PowerShell is the default shell, the important $PATH additions - possibly among other initializations - won't happen even for local shells - see https://github.com/PowerShell/PowerShell/issues/6027#issuecomment-360638685

  • It is debatable whether PowerShell should be expected to honor ~/.profile or even $ENV. Arguably, only honoring /etc/profile is sufficient, as an exception, due to its shell-transcendent importance.

The approach proposed by @joeyaiello is robust with respect to honoring the _environment variables_, but:

  • it comes with a performance penalty that on macOS _every_ terminal-program created instance will pay (with PowerShell as the default shell); however, it doesn't apply to direct invocation of powershell.

  • the honoring may not be complete, as /etc/profile may contain commands such as umask and ulimit (not sure what other commands need to be considered).

For PowerShell to detect being invoked as a _login_ shell it must examine the equivalent of $0 (argv[0]) - the invocation name - to look for - as the 1st char., because that is the mechanism used by:

  • ssh
  • macOS terminal programs
  • Bash's exec builtin when a shell is spawned with the -l option.

[Environment]::CommandLine doesn't reflect such such a custom invocation name, however (it contains the path to PowerShell _DLL_, which is a bug yet to be resolved - see https://github.com/dotnet/coreclr/issues/20562). As a workaround, PowerShell could determine its invocation name via the standard ps utility, using command ps -o comm= $PID.

Additionally, the major POSIX-like shells also support explicit invocation with option -l to create a login shell - PowerShell has no equivalent.
_Update: -l is now supported - see #9528_


Finally, PowerShell's current startup behavior with respect to profiles (and other aspects) is fundamentally at odds with that of POSIX-like shells in that it loads its profiles _by default_, irrespective of whether the shell is interactive or not (or a login shell, which PowerShell has no concept of) - see #3743

This means, for instance, that with PowerShell as the default shell, $PROFILE is also sourced in the invisible login shell instance that is created on OS session startup on Linux. (see above).

Really /etc/profile and ~/.profile is Bash scripts - we can not directly interprete it in PowerShell. I wonder why we should depend on Bash script files? csh/tcsh use their files (/etc/.login and ~/.login ). We can do the same and use /etc/powershell.login.json and ~/.powershell.login.json files.

@iSazonov:

I've updated my comment above to reflect recent findings:

On _Linux_, given how /etc/profile works there (disclaimer: I've only looked at Ubuntu 16.04), I think we're all set. , _locally created_ shell sessions wouldn't need to process /etc/process again, but sessions started _remotely_, via ssh, would.

On _macOS_, _every_ login shell session (whether local or remote) is expected to process /etc/profile:

As stated, even calling out to sh and copying the resulting environment variables wouldn't cover other initializations users _may_ have added to /etc/profile, such as a umask statement (see https://github.com/PowerShell/PowerShell/issues/6679#issuecomment-383204763) or a ulimit statement.

Looks like more people are hitting this now. My proposed fix for PS7 is to build on https://github.com/PowerShell/PowerShell/pull/9528 so if -l is used, pwsh will shell out to sh and apply those env vars to itself. umask and other environment factors will not be taken into account pending additional user feedback and addressed case-by-case.

It looks as workaround without RFC defined PowerShell as login shell. Having the RFC Unix distributive maintainers can expose paths in standard way. Ex.: we could have a ps_path_helper utility per Unix distributive and call it at PowerShell login shell startup.

There is a difference between .bash_profile and .profile
https://askubuntu.com/questions/60218/how-to-add-a-directory-to-the-path/226947

I mean that $PATH can be different for login and non-login shells.

Related #10050

:tada:This issue was addressed in #10050, which has now been successfully released as v7.0.0-preview.3.:tada:

Handy links:

Why does pwsh not pick up the environment variables from bash when it is launched??

[mee foo]$ echo $NODE_EXTRA_CA_CERTS
/cafile.pem
[me foo]$ pwsh
PS /foo> $env:NODE_EXTRA_CA_CERTS

Crickets...

@cawoodm If you ask about default shell just this was used in #10050.

@iSazonov, @cawoodm's problem is unrelated to this issue.

@cawoodm, are you sure that $NODE_EXTRA_CA_CERTS is defined as an _environment_ variable (typically created with export), and not just as a _shell_ variable? Shell variables aren't seen by child processes.

Try the following (from Bash or any other POSIX-like shell) to verify that a true environment variable is indeed inherited by PowerShell (this syntax creates a command-scoped environment variable):

$ NODE_EXTRA_CA_CERTS=foo pwsh -noprofile -c '$env:NODE_EXTRA_CA_CERTS'
foo

Also note that such inheritance is a system-level feature that exists independently of PowerShell.

If you have a reproducible case where this inheritance indeed doesn't work, please create a new issue.

To me there is no difference: whatever environment variables I have (echo $foo) should be available in child processes like pwsh. PowerShell should not load some other environment - that would break stuff.

Now, we're using WSL and it does not seem to respect /etc/environment so we have added the following to .bashrc:

NODE_EXTRA_CA_CERTS=foo

To be honest I do not know the difference between bar=foo and export bar=foo...

Checking it with bash:

$ echo $NODE_EXTRA_CA_CERTS
foo

Good, checking it with pwsh:

$ pwsh -noprofile -c '$env:NODE_EXTRA_CA_CERTS'

Not Good.

My expectation would be that the current environment is passed to pwsh. I think this is normal behaviour for child processes.

My expectation would be that the current environment is passed to pwsh

_Shell_(-only) variables are not part of the system-level _process environment_, which is the only environment child processes inherit. Shell variables are by design limited to the given shell process.

While in cmd.exe _all_ variables are environment variables, both PowerShell and POSIX-like shells such as bash distinguish between shell(-only) variables and environment variables.

While In PowerShell you always use the same, distinct syntax for both creating and querying environment variables ($env:...), in POSIX like shells _both_ types of variables are _queried_ the same, namely with just the $ sigil, as in $NODE_EXTRA_CA_CERTS; that is when you _get_ a variable value, the syntax isn't telling you whether you're accessing a _shell_(-only) or an _environment variable_.

In POSIX-like shells, it's the _declaration_ of a variable that matters:

As a _stand-alone statement_, NODE_EXTRA_CA_CERTS=foo creates a _shell_(-only) variable - by design, no child process will see it.

What I used above - _prepending_ variable assignments directly to a command invocation (NODE_EXTRA_CA_CERTS=foo pwsh ....) - is _syntactic sugar_ for defining _command-scoped_ environment variables - only the child process created sees them, not the remainder of the script.

If you want to use a stand-alone statement to create an environment variable, use the export utility:

export NODE_EXTRA_CA_CERTS=foo

In POSIX-like shells:

  • To see all _environment_ variables, run env (in PowerShell you'd use Get-ChildItem env:)

  • To check whether a given variable is an environment variable, use typeset -p <name> (works in bash, ksh, zsh): if the output contains either -x or export (zsh), the variable is an environment variable (in PowerShell you'd use Get-Item env:<name>).

Thanks @mklement0 - you da man!

Was this page helpful?
0 / 5 - 0 ratings