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

Pal*_*cil 8 powershell environment-variables

目的

环境变量更改隔离到代码块.

背景

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

setlocal
set MYLANG=EN
my-cmd dostuff -o out.csv
endlocal
Run Code Online (Sandbox Code Playgroud)

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

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

我目前的方法:

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

函数范围并不胜过引用 $env:

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

($env:TEST)
tt
($env:TEST)
Run Code Online (Sandbox Code Playgroud)

输出:

C:\Users\me> .\eg.ps1
EN
GB
GB
Run Code Online (Sandbox Code Playgroud)

mkl*_*nt0 7

批处理文件,所有的变量是环境变量太多; 因此,也setlocal ... endlocal为环境变量提供了局部作用域。

相比之下,在PowerShell中外壳变量(如$var)是不同环境变量(例如,$env:PATH -一个区别是通常有利的

鉴于最小的设置环境变量的作用域是当前过程-因此整个的PowerShell会话,你必须管理一个较小的自定义范围手动,如果你想这样做的过程(这是setlocal ... endlocal在做cmd.exe,为此PowerShell有没有内置等效项;对于自定义范围的shell变量,请使用& { $var = ...; ... }):

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

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

& { 
  $oldVal, $env:MYLANG = $env:MYLANG, 'EN'
  my-cmd dostuff -o out.csv
  $env:MYLANG = $oldVal 
}
Run Code Online (Sandbox Code Playgroud)

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

& { $env:MYLANG='EN'; my-cmd dostuff -o out.csv; $env:MYLANG=$null }
Run Code Online (Sandbox Code Playgroud)

$oldVal, $env:MYLANG = $env:MYLANG, 'EN'在将值更改为$env:MYLANG$oldVal同时保存in的旧值(如果有)'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
}
Run Code Online (Sandbox Code Playgroud)

但是请注意,如果您只使用临时修改的环境调用外部程序,则不需要try/ catch,因为从 PowerShell 7.1 开始,外部程序永远不会导致 PowerShell 错误,尽管将来可能会发生变化

为了促进这种方法,这个相关问题的答案提供了便利功能
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 }
Run Code Online (Sandbox Code Playgroud)

替代方案,使用辅助过程

通过使用辅助进程并仅在那里设置瞬态环境变量,

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

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

使用辅助。cmd.exe过程:

cmd /c "set `"MYLANG=EN`" & my-cmd dostuff -o out.csv"
Run Code Online (Sandbox Code Playgroud)

笔记:

  • "..."选择了外部引用,以便您可以在命令中引用 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 }
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 启动另一个 PowerShell 会话的开销很大。

  • 脚本块 ( { ... }) 的输出在调用范围内进行序列化和反序列化;对于字符串输出,这无关紧要,但是复杂的对象(例如[System.IO.FileInfo]反序列化为原始对象的模拟)(可能有也可能没有问题)。