PowerShell:仅为单个命令设置环境变量

237

在Linux上,我可以执行:

$ FOO=BAR ./myscript

如何在PowerShell中调用“myscript”并设置环境变量FOO?

类似的操作是否在PowerShell中也可以实现,即不必先设置变量,然后调用命令,再取消变量设定?

更明确地说,我的使用场景是:我不想将其作为脚本的一部分。相反,我有一个第三方脚本,可以通过环境变量来控制其行为,但是在这种情况下无法使用命令行参数。因此,希望能够通过输入以下命令之一来轻松更改:

$ OPTION=1 ./myscript

并且

$ ./myscript

这将非常方便。


1
我猜我的问题是为什么你需要这样做?我认为有更好的解决方案。 - EBGreen
70
@EBGreen,那通常不是一个有帮助的问题。UNIX shell具备该能力表明它有用处。我的想法是:控制git用于提交的用户名和电子邮件地址。对于这些,没有命令行选项 - 必须在~/.gitconfig、每个仓库中的.git/config或envars中设置它们。其中,envars 明显更容易实时设置(并方便地覆盖文件中的值)。因此,如果我想在PowerShell中为一个“git commit”更改作者名称,应该怎么做? - Mark Reed
12
完全同意询问这个是否必要是毫无意义的。在Linux命令行执行时,这是再普通不过的事情了,而我们现在被迫使用powershell(如果存在语法噩梦,那它就是其中之一)的人们不得不不断地寻找明显技巧的答案。大多数时候,除非你编写长脚本来完成琐碎的事情,否则它们在powershell中根本不存在。对于这个Shell让我感到深深的沮丧... - Kim
6
此功能正在讨论是否加入PowerShell 6中。 - Franklin Yu
2
谢谢链接,@FranklinYu,但目前来看,这将是在v7.0之后的_希望不久的将来版本中_. - mklement0
10个回答

144

一般来说,最好通过参数将信息传递给脚本,而不是使用全局(环境)变量。但如果这是你需要做的事情,可以按照以下方式进行:

$env:FOO = 'BAR'; ./myscript

您可以通过以下方式稍后删除环境变量$env:FOO:

Remove-Item Env:\FOO

3
只是一种想法:你能不能生成一个新的PowerShell进程,通过"-Command"参数将脚本块传递给它?这样你就不用清理环境了,因为那会包含在子进程中。虽然我正在和一个PowerShell MVP交谈,所以这可能行不通 :-) - Joey
2
Keith,我们在Pscx中有一个push-environmentblock和pop-environmentblock,正是为了这种情况而设计的;-) - x0n
2
Johannes,那也可以,但有点像作弊。 :-) PSCX Push/Pop-EnvironmentBlock(我改变的那个)可以工作,但它没有脚本块的自动清理支持。 - Keith Hill
2
你不需要将 env:foo 删除,而是将其设置回旧值(或者取消设置)吗? - chwarr
4
正如@Joey所提到的,如果你想干净地使用环境变量,你可以这样做:& {$pre = $env:foo; $env:foo = 'bar'; ./myscript; if ($pre) {$env:foo = $pre} else {Remove-Item env:\foo}... 一些人可能会说这种方法有点笨重,但可以避免副作用。 - Lucas
显示剩余4条评论

43

一行代码实现的2种简单方法:

$env:FOO='BAR'; .\myscript; $env:FOO=$null
$env:FOO='BAR'; .\myscript; Remove-Item Env:\FOO

仅是从其他答案中(感谢大家)汇总信息,由于某些原因它们不包含纯粹的一行指令。


7
不起作用 - 变量未被移除。 - pishpish
第二种方法对我有效,Windows 11。 - realtebo
6
请使用$env:FOO=$null取代$env:FOO=''。该操作意为清空环境变量的值。 - Dois
由于环境变量不能是空字符串,将其设置为 $null 或空字符串会将其删除。请参考 https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-7.3#using-the-variable-syntax。 - James Skemp
@Dois 我已经修复了答案,但是正如James Skemp所引用的那样,将$env:FOO设置为空字符串会产生与变量删除相同的效果。 - Alexander Fadeev

32
通过使用脚本块来调用 Powershell 创建“子 shell” 可以将环境的更改限定在特定范围内。
pwsh -Command { $env:MYVAR="myvalue"; .\path\to.exe }

2
这是唯一一个不会对全球环境造成污染的答案,即使是暂时的,除了使用起来比较困难的Start-Job答案。 - undefined
1
也许你需要使用 powershell 而不是 pwsh - undefined
1
pwsh是某种别名吗?在当前的PowerShell中,该命令是未知的。 - undefined
1
@Andreas,pwshPowerShell (Core) 7+ 的可执行文件名称。在内置的 Windows PowerShell 中,请使用 powershell.exe。(这两个命令行界面在参数方面基本相同。)如果您想调用与当前会话基础相同的可执行文件,请使用 & (Get-Process -Id $PID).Path { $env:MYVAR="myvalue"; .\path\to.exe }。启动另一个 PowerShell 实例会导致性能损失。 - undefined

29

我对这个问题感到足够的动力,因此我继续编写了一个脚本:with-env.ps1

用法:

with-env.ps1 FOO=foo BAR=bar your command here

# Supports dot-env files as well
with-env.ps1 .\.env OTHER_ENV=env command here

另一方面,如果您安装了Gow,您可以使用env.exe,它可能比我上面编写的快速脚本更加健壮。

用法:

env.exe FOO=foo BAR=bar your command here

# To use it with dot-env files
env.exe $(cat .env | grep.exe -v '^#') SOME_OTHER_ENV=val your command

它不一定是Gow,但它也可以在Windows上的Git上运行,因为它提供了env.exe。 - Phani Rithvij

10
为了达到类似Unix语法的效果,你不仅需要设置环境变量,而且在执行命令后还需要将其重置为原来的值。我通过向PowerShell配置文件添加以下类似函数的方式,已经为我常用的命令完成了这个任务。
function cmd_special()
{
  $orig_master = $env:app_master
  $env:app_master = 'http://host.example.com'
  mycmd $args
  $env:app_master = $orig_master
}

所以mycmd是一个可执行文件,根据环境变量app_master的值而表现不同。通过定义cmd_special,我现在可以从命令行(包括其他参数)执行cmd_special,同时将app_master环境变量设置... 在命令执行后它会被重置(甚至取消设置)。

可以推测,您也可以针对单个调用进行临时操作。

& { $orig_master = $env:appmaster; $env:app_master = 'http://host.example.com'; mycmd $args; $env:app_master = $orig_master }

这应该比现在的情况更容易,但显然PowerShell不支持这种用例。或许将来的版本(或第三方功能)会促进这一用例。如果PowerShell有一个能够做到这一点的cmdlet就好了,例如:

with-env app_master='http://host.example.com' mycmd

也许 PowerShell 大师可以建议如何编写这样的 cmdlet。

6

您可以通过将脚本作为作业来实现此操作:

Start-Job -InitializationScript { $env:FOO = 'BAR' } -FilePath .\myscript.ps1 |
    Receive-Job -Wait -AutoRemoveJob

你还可以使用 Start-JobArgumentList 参数向脚本传递参数:
$jobArgs = @{
    InitializationScript = { $env:FOO = 'BAR' } 
    FilePath             = '.\myscript.ps1'
    ArgumentList         = 'arg1', 'arg2' 
}
Start-Job @jobArgs | Receive-Job -Wait -AutoRemoveJob

优点和缺点

  • 脚本运行结束后不需要重置环境变量(即使在存在异常的情况下,也需要使用try/finally来正确完成)。
  • 该环境变量仅适用于启动的脚本。它不会影响其他可能并行启动的作业。
  • 脚本将在其自己的、有些隔离的环境中运行。这意味着启动的脚本不能设置主脚本的变量,它必须写入成功流 (通过隐式方式或调用已经写入成功流的另一个命令) 来与主脚本通信。这可能是优点,也可能是缺点,具体取决于使用情况。

是否可能使用需要用户输入的交互式命令来实现类似的功能? - Igor

0
在我的使用场景中,我需要设置一个环境变量,以便在Docker Compose脚本中使用它。在我的Powershell脚本中,我定义了这个变量,使用分号然后在同一行调用docker-compose。
$env:PLATFORM="linux/x86_64" ; docker-compose up -d --build

在 Docker Compose 中,我现在可以直接使用我的 ${PLATFORM} 变量。
它看起来像这样:
...
services:
  zookeeper:
    image: confluentinc/cp-zookeeper:latest
    platform: ${PLATFORM}
...

0

一个工具使用了一些无法通过pip获得的库。为了将路径保存在易记的位置,我在全局设置了$env:PYTHONPATHWAIT。

使用方式如下:

$env:PYTHONPATH=$env:PYTHONPATHWAIT ; usdcat .\milkyway.usd

这会在运行命令之前设置变量,但不会在命令完成时取消设置该变量,正如问题所要求的。 - jkmartindale

0
考虑到CMD是Windows内核上的本地CLI(并且仍然是许多工具的自动化接口),您可能正在使用CMD提示符或接受CMD控制台语句的界面,通过powershell.exe执行PowerShell脚本。
如果您正在使用-File参数将脚本传递给powershell.exe,则不能使用其他PowerShell代码来设置脚本访问的环境变量,因此您可以在调用powershell.exe之前在CMD环境中设置您的环境变量。
> set foo=bar && powershell.exe -File .\script.ps1

一个单独的&也可以工作,但如果set由于某种原因失败,则允许命令继续执行。(这真的可能吗?我不知道。)
此外,将"foo=bar"用引号括起来可能更安全,以便不会将后面的内容作为变量内容传递给set

-7
你可以将变量限定在函数和脚本范围内。
$script:foo = "foo"
$foo
$function:functionVariable = "v"
$functionVariable

New-Variable还有一个-scope参数,如果你想要正式地声明变量并使用new-variable来创建。


2
嗯...我认为这个答案不适用于此处。PowerShell变量并不是环境变量。根据问题,提问者并没有将环境变量用作临时空间(就像在CMD中一样,如果是这种情况,这个答案就适用了),而是需要在调用外部命令之前操纵环境,然后在之后恢复环境(或类似的操作)。 - chwarr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接