PowerShell的setlocal / endlocal等价物是什么?

时间:2018-01-24 22:21:18

标签: powershell environment-variables

目标

隔离环境变量对代码块的更改。

背景

如果我想创建批处理脚本来运行需要环境变量集的命令,我知道我可以这样做:

setlocal
set MYLANG=EN
my-cmd dostuff -o out.csv
endlocal

但是,当我需要使用shell脚本语言时,我倾向于使用PowerShell。我知道如何设置环境变量($env:TEST="EN"),当然这只是一个简单的例子。但是,我不确定如何使用批处理脚本实现相同的效果。令人惊讶的是,我也没有看到任何问题。

我知道设置$env:TEST="EN"的内容是进程范围的,但如果我在单个终端会话中使用脚本作为小实用程序,则这是不切实际的。

我目前的方法:

  1. 已输入setlocal。但那不是一个小虫......我希望。
  2. 将当前变量保存到临时变量,运行我的命令,将其更改回来...有点傻。
  3. 功能级别范围(虽然我怀疑成功,因为$env:似乎与$global:没什么不同)
  4. 功能范围不会超过对$env:

    的引用
    $env:TEST="EN"
    function tt {
      $env:TEST="GB"
      ($env:TEST)
    }
    
    ($env:TEST)
    tt
    ($env:TEST)
    

    输出:

    C:\Users\me> .\eg.ps1
    EN
    GB
    GB
    

4 个答案:

答案 0 :(得分:4)

批处理文件中,所有 shell 变量都是环境变量 ;因此,setlocal ... endlocal也为环境变量提供了本地范围。

相比之下,在 PowerShell 中, shell 变量(例如$var)与不同环境变量(例如,$env:PATH - 这种区别通常是有益的

鉴于用于设置环境变量的 最小范围是当前进程 - 因此整个PowerShell 会话 必须手动管理较小的自定义范围 ,如果您要在进程中执行此操作(setlocal ... endlocalcmd.exe中,PowerShell没有内置的等价物;自定义范围 shell 变量,使用& { $var = ...; ... }):

进程内方法:自定义范围的手动管理:

为了稍微减轻痛苦,您可以使用脚本块({ ... })来提供命令的独特可视分组,当使用&调用时,也会创建新的本地范围,因此任何辅助。您在脚本块中定义的变量会自动超出范围(您可以将其写为带有;的单行 - 分隔命令):

& { 
  $oldVal, $env:MYLANG = $env:MYLANG, 'EN'
  my-cmd dostuff -o out.csv
  $env:MYLANG = $oldVal 
}

更简单地说,如果没有必须恢复的预先存在的MYLANG值:

& { $env:MYLANG='EN'; my-cmd dostuff -o out.csv; $env:MYLANG=$null }

$oldVal, $env:MYLANG = $env:MYLANG, 'EN'会将$env:MYLANG中的旧值(如果有)保存在$oldVal中,同时将值更改为'EN';这种分配给多个变量的技术(在某些语言中称为解构赋值)在Get-Help about_Assignment_Operators“分配多个变量”一节中进行了解释。

更合适,更强大但更详细的解决方案是使用 try { ... } finally { ... }

try {
  # Temporarily set/create $env:MYLANG to 'EN'
  $prevVal = $env:MYLANG; $env:MYLANG = 'EN'

  my-cmd dostuff -o out.csv  # run the command(s) that should see $env:MYLANG as 'EN'

} finally { # remove / restore the temporary value
  # Note: if $env:MYLANG didn't previously exist, $prevVal is $null,
  #       and assigning that back to $env:MYLANG *removes* it, as desired.
  $env:MYLANG = $prevVal
}

但请注意,如果您只使用临时修改的环境调用外部程序,则不需要try / catch,因为外部程序从不导致PowerShell错误。

为方便这种方法,我的this answer提到了一个相关的问题便利功能
Invoke-WithEnvironment
,它允许您编写相同的调用:

# Define env. var. $env:MYLANG only for the duration of executing the commands
# in { ... }
Invoke-WithEnvironment @{ MYLANG = 'EN' } { my-cmd dostuff -o out.csv }

替代方案,使用辅助流程

通过使用辅助过程并仅设置瞬态环境变量

  • 您无需在调用后恢复环境

  • 但是您会支付性能损失,并且调用复杂性会增加。

使用辅助。 cmd.exe进程:

cmd /c "set `"MYLANG=EN`" & my-cmd dostuff -o out.csv"

注意:

  • 选择了外"..."引用,以便您可以在命令中引用PowerShell变量;然后必须将嵌入式"转义为`"

  • 此外,必须根据 cmd.exe规则传递目标命令的参数(与手头的简单命令没有区别)。

使用辅助。子PowerShell会话:

# In PowerShell *Core*, use `pwsh` in lieu of `powershell`
powershell -nop -c { $env:MYLANG = 'EN'; my-cmd dostuff -o out.csv }

注意:

  • 启动另一个PowerShell会话非常昂贵。

  • 脚本块({ ... })的输出可以在调用范围内进行序列化和后续反序列化;对于 string 输出,这无关紧要,但复杂的对象(如[System.IO.FileInfo])反序列化为原始的仿真(这可能是也可能不是问题)。

答案 1 :(得分:1)

有一种方法可以在PowerShell中实现这一目标:

本地范围:

& { [System.Environment]::SetEnvironmentVariable('TEST', 'WORK Local', [System.EnvironmentVariableTarget]::Process)
[System.Environment]::GetEnvironmentVariable("TEST", [System.EnvironmentVariableTarget]::Process) }

这将在与上述相同的过程范围内创建环境变量。在范围之外的任何调用都不会返回任何内容。

对于全局目标,您只需将目标更改为计算机:

& { [System.Environment]::SetEnvironmentVariable('TEST', 'WORK Global', [System.EnvironmentVariableTarget]::Machine) }

在范围之外对此进行的任何调用都将返回' Work Global'

全部放在一起:

## create local variable and print
& { [System.Environment]::SetEnvironmentVariable('TEST', 'WORK Local', [System.EnvironmentVariableTarget]::Process)
[System.Environment]::GetEnvironmentVariable("TEST", [System.EnvironmentVariableTarget]::Process) }


function tt {
  ($env:TEST)
}

& { $TEST="EN"; $env:TEST="EN"; tt }
& { $TEST="change1"; $env:TEST="change1"; tt }
& { $TEST="change1"; $env:TEST="change2"; tt }

[System.Environment]::GetEnvironmentVariable("TEST", [System.EnvironmentVariableTarget]::Process)

& { [System.Environment]::SetEnvironmentVariable('TEST', 'WORK Global', [System.EnvironmentVariableTarget]::Machine) } ## create global variable

## Create local variable and print ( overrides global )
& { [System.Environment]::SetEnvironmentVariable('TEST', 'WORK Local', [System.EnvironmentVariableTarget]::Process)
[System.Environment]::GetEnvironmentVariable("TEST", [System.EnvironmentVariableTarget]::Process) }

[System.Environment]::GetEnvironmentVariable("TEST", [System.EnvironmentVariableTarget]::Machine) ## get global variable

[System.Environment]::SetEnvironmentVariable("TEST",$null,"USer") ## remove global variable

这给了我们以下输出:

WORK Local
EN
change1
change2
change2
WORK Local
WORK Global

答案 2 :(得分:1)

我可能会使用try { } finally { }

try {
    $OriginalValue = $env:MYLANG
    $env:MYLANG= 'GB'
    my-cmd dostuff -o out.csv
}
finally {
    $env:MYLANG = $OriginalValue
}

即使脚本中遇到错误,也应该强制将值设置回原始值。它不是防弹的,但是大多数可以打破这种情况的事情也会非常明显地出现问题。

你也可以这样做:

try {
    $env:MYLANG= 'GB'
    my-cmd dostuff -o out.csv
}
finally {
    $env:MYLANG = [System.Environment]::GetEnvironmentVariable('MYLANG', 'User')
}

那应该从HKEY_CURRENT_USER\Environment检索值。您可能需要'Machine'而不是'User',这将从HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment开始。您需要的是它是用户环境变量还是计算机环境变量。这是有效的,因为Env:提供程序驱动器不会持久保存环境变量,因此对这些变量的更改不会更改注册表。

答案 3 :(得分:-2)

请原谅我,如果我错过了一些内容,因为这篇文章的某些内容我有点不清楚。

我会使用范围修改$local$script$global修饰符。

示例

$env:TEST="EN"
function tt {
  $local:env:TEST="GB"
  ($local:env:TEST)
}

$t = {
  $local:env:TEST="DE"
  ($local:env:TEST)
}

($env:TEST)
tt
($env:TEST)
. $t
($env:TEST)

带注释的输出

EN     # ($env:TEST)
GB     # tt
EN     # ($env:TEST)
DE     # . $t
EN     # ($env:TEST)