目标
隔离环境变量对代码块的更改。
背景
如果我想创建批处理脚本来运行需要环境变量集的命令,我知道我可以这样做:
setlocal
set MYLANG=EN
my-cmd dostuff -o out.csv
endlocal
但是,当我需要使用shell脚本语言时,我倾向于使用PowerShell。我知道如何设置环境变量($env:TEST="EN"
),当然这只是一个简单的例子。但是,我不确定如何使用批处理脚本实现相同的效果。令人惊讶的是,我也没有看到任何问题。
我知道设置$env:TEST="EN"
的内容是进程范围的,但如果我在单个终端会话中使用脚本作为小实用程序,则这是不切实际的。
我目前的方法:
setlocal
。但那不是一个小虫......我希望。$env:
似乎与$global:
没什么不同)功能范围不会超过对$env:
$env:TEST="EN"
function tt {
$env:TEST="GB"
($env:TEST)
}
($env:TEST)
tt
($env:TEST)
输出:
C:\Users\me> .\eg.ps1
EN
GB
GB
答案 0 :(得分:4)
在批处理文件中,所有 shell 变量都是环境变量 ;因此,setlocal ... endlocal
也为环境变量提供了本地范围。
相比之下,在 PowerShell 中, shell 变量(例如$var
)与的不同环境变量(例如,$env:PATH
) - 这种区别通常是有益的。
鉴于用于设置环境变量的 最小范围是当前进程 - 因此整个PowerShell 会话 ,必须手动管理较小的自定义范围 ,如果您要在进程中执行此操作(setlocal ... endlocal
在cmd.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)