简体   繁体   中英

PowerShell flexibility in scope of environment variables via scripts

In bash, if I define a variable inside a script (say, set_var.sh ), I can choose whether those definitions persist after "running" the script. This will depend on how I "run" the script, the options being:

  1. sh set_var.sh (variables do not persist)
  2. ./set_var.sh (variables do not persist, same as point 1)
  3. source set_var.sh (variables persist)

This is irrespective from the variable being exported to the environment or not in set_var.sh .

Can the same as items 1 and 3 above be achieved with PowerShell 5.1 scripts, for both PS and $env variables?

For an illustration see (1) below.


EDIT :

As per answer by Mathias R. Jessen, the equivalent to item #3 above is "dot sourcing". There is also an "intermediate" case (not identified above, perhaps there is also a way to get this in bash), where environment variables persist but PS variables don't.

My script:

# set_var.ps1
$env:TEST_VAR = 'test_var'
$TEST_VAR2 = 'test_var2'

What I checked:

> $env:TEST_VAR ; $TEST_VAR2 ;
> . .\set_var.ps1
> $env:TEST_VAR ; $TEST_VAR2 ;
test_var
test_var2
> Remove-Variable TEST_VAR2 ; Remove-Item env:TEST_VAR ;
> $env:TEST_VAR ; $TEST_VAR2 ;
> .\set_var.ps1
> $env:TEST_VAR ; $TEST_VAR2 ;
test_var
> Remove-Item env:TEST_VAR ;
> $env:TEST_VAR ; $TEST_VAR2 ;
> & .\set_var.ps1
> $env:TEST_VAR ; $TEST_VAR2 ;
test_var

(1) Example of persistent / non-persistent variables

I have script set_var.sh with the following contents:

#!/bin/bash

export TEST_VAR=test_var
TEST_VAR2=test_var2

Then the following commands prove my point:

$ echo $TEST_VAR ; echo $TEST_VAR2


$ sh set_var.sh ; echo $TEST_VAR ; echo $TEST_VAR2


$ source set_var.sh ; echo $TEST_VAR ; echo $TEST_VAR2
test_var
test_var2
$ echo $TEST_VAR ; echo $TEST_VAR2
test_var
test_var2
$ env | grep TEST_VAR
TEST_VAR=test_var
$ unset TEST_VAR ; unset TEST_VAR2
$ echo $TEST_VAR ; echo $TEST_VAR2


$ ./set_var.sh ; echo $TEST_VAR ; echo $TEST_VAR2


$ echo $TEST_VAR ; echo $TEST_VAR2


You want . - the dot-sourcing operator - which executes your script or command in the caller's scope, thereby persisting all variables after execution:

# set_vars.ps1
$TEST_VAR = 123
# script.ps1
. $PSScriptRoot/set_vars.ps1
echo $TEST_VAR # outputs 123

To avoid locally defined variables persisting after execution, either execute the script as-is, or by explicitly using the & invocation operator:

# script.ps1
& $PSScriptRoot/set_vars.ps1
echo $TEST_VAR 
# outputs an empty string, assuming $TEST_VAR was not already defined in the callers scope

To complement Mathias' helpful answer :

Regarding your edit:

It is a script's direct invocation / invocation via & - which, unlike in sh / bash , runs in-process - preserves environment-variable changes, but not regular variables in the script. [1]

Using & with a script block ( {... } ) in lieu of a script file for a succinct demonstration:

PS> & { $foo = 'bar'; $env:foo = 'bar-env' }; "[$foo]", "[$env:foo]"
[]        # regular variable in the script [block] scope went out of scope
[bar-env] # environment variable is visible to caller (the whole *process*)

As for the PowerShell equivalent of 1. ( sh set_var.sh ) and 2. ( ./set_var.sh ):

An explicit call to the PowerShell CLI ( powershell.exe in Windows PowerShell, pwsh in PowerShell [Core], v6+): is needed to run a script in a child process .

Note: The following examples use pwsh , but the same applies to powershell.exe (whose CLI is a mostly identical to pwsh 's, with only a few parameters having been added to the latter).

In the simplest case, use -File (which is the implied parameter in PowerShell [Core], but is required in Windows PowerShell):

pwsh -File set_var.ps1  # PowerShell v6+: same as: pwsh set_var.ps1

Note, however, that you'll only get textual output (strings) from the script this way rather than the rich objects that PowerShell normally supports.

The simplest way to receive objects instead - available from inside PowerShell only [2] - is to pass a script block in which the script is called instead, which automatically makes the CLI output XML-serialized representations of the script's output objects, and causes these representations to be automatically deserialized by the calling session:

pwsh { .\set_var.ps1 } 

Note:

  • The .\ prefix is necessary, because PowerShell - by design, as a security feature - does not permit running scripts by mere file name from the current directory .

  • The XML-based serialization using the CLIXML format - which is also used for PowerShell's remoting and background jobs - has limits with respect to type fidelity ; except for .NET primitive types and a handful well-known types, you get [pscustomobject] -based approximations of the original objects - see this answer .


[1] However, PowerShell does offer the ability to set variables across in-session scope boundaries, eg $global:foo = 'bar' to set a session-global variable or Set-Variable foo bar -Scope 1 to set a variable in the caller's (parent) scope (which when called from a script's top-level scope would also be the global scope).

[2] From outside of PowerShell, you can explicitly request output in CLIXML format (the XML-based object-serialization format), namely with the -of Xml CLI parameter, but you'll have to parse the XML yourself and reconstruct objects from it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM