为啥 PowerShell 不处理这个 $?正确退出代码?
Posted
技术标签:
【中文标题】为啥 PowerShell 不处理这个 $?正确退出代码?【英文标题】:Why is PowerShell not treating this $? exit code correctly?为什么 PowerShell 不处理这个 $?正确退出代码? 【发布时间】:2021-09-13 18:30:19 【问题描述】:我想编写一个 PowerShell 脚本来选择 JFrog 服务连接,但该脚本的行为与我期望的不同。
当我手动运行jfrog config use
命令时,它按预期工作:
PS C:\Users\rdepew> jfrog config use Dummy
[Info] Using server ID 'Dummy' (https://foo.jfrog.io/).
PS C:\Users\rdepew> $?
True
我希望能够使用始终返回 true 或 false 的 $? 来检测 jfrog config use
是否已成功执行。这个 sn-p 说明了我遇到的问题:
PS C:\Users\rdepew> if (jfrog config use Dummy)
>> Write-Host Dummy exists
>> else
>> Write-Host Dummy doesnt exist but I dont believe it
>>
[Info] Using server ID 'Dummy' (https://foo.jfrog.io/).
Dummy doesnt exist but I dont believe it
[Info] 行表明 JFrog 实际上正在使用服务器连接“Dummy”。我假设 $?退出代码评估为真,就像在我的手动执行中一样。但我的 if/else 测试采用“if false”分支。为什么?
更多信息:如果我使用不存在的服务连接的名称,我会得到预期的行为:
PS C:\Users\rdepew> if (jfrog config use Bogus)
>> Write-Host Bogus exists
>> else
>> Write-Host Bogus doesnt exist
>>
[Error] Could not find a server with ID 'Bogus'.
Bogus doesnt exist
我不明白为什么 PS 在 True 和 False 情况下都跳到 else
子句。
更多信息
感谢第一个答案和cmets,我明白了一点。我可以像这样重写我的 sn-p:
jfrog config add Dummy
if ($LASTEXITCODE=0)
etc.
但我仍然对 $ 感到困惑?行为。从命令行,$?行为如下:
PS C:\Users\rdepew> jfrog config use Dummy
[Info] Using server ID 'Dummy' (https://foo.jfrog.io/).
PS C:\Users\rdepew> $?
True
PS C:\Users\rdepew> jfrog config use Bogus
[Error] Could not find a server with ID 'Bogus'.
PS C:\Users\rdepew> $?
False
...这是人们所期望的。
【问题讨论】:
$?
告诉您最后一个命令是否成功完成。仅当jfrog config use Dummy
将任何对象/字符串返回到成功流时,if (jfrog config use Dummy)
才会为真
@Daniel 这实际上不是真的。如果最后一个命令是外部命令并且$LASTEXITCODE
的值为0
,$?
应该返回$?
$?
应该返回$False
任何其他值。
@BendertheGreatest,我明白了。在大多数情况下,这不是基本相同的吗?本地应用程序通常使用 $LASTEXITCODE 0 来表示成功,而任何其他代码都表示失败。我知道并非所有应用程序都严格遵守这一点,但在大多数情况下,这是意料之中的。无论如何,我的观点是 $?
和 if (jfrog config use Dummy)
不是一回事。后者仅当在成功流中接收到对象时才会返回 true,并且与命令是否成功无关。
我完全误读了您的第一条评论,我的错。我以为你说 cmdlet 不是命令
(快速搜索“成功流”)哦!因此,如果 jfrog 在错误流上输出其错误消息,则一切都按预期进行。如果 jfrog 在 Success 流上输出它的 Info 消息,那么一切都会按预期运行。但是,如果 jfrog 在信息流(因此,Info
)而不是成功流上输出其信息消息,则可能会导致我看到的行为。对吗?
【参考方案1】:
在 PowerShell 中,如果之前的 cmdlet 完成且没有错误,$?
返回 $true
,如果出现任何错误,则返回 $false
。对于外部命令,它会返回$LASTEXITCODE -eq 0
的结果。
但是,当您将命令(外部或其他)作为条件放入if
语句时,它不会检查命令是否成功,而是检查结果的真实性。如果命令返回正确的 $true
或 $false
布尔值,这很简单,否则,它将成功流上的输出转换为布尔值。除以下输出之外的任何输出都计算为$true
和$null
、0
、空字符串和空数组计算为$false
。
我不确定的是为什么这两个命令至少在以if
条件运行时都没有产生$true
输出。另一种编写代码的方法是在if
之外运行jfrog
,然后检查$LASTEXITCODE
的结果:
$output = jfrog config use Dummy
if ( $LASTEXITCODE -eq 0 )
Write-Host "Dummy exists"
else
Write-Host "Dummy doesnt exist but I dont believe it"
如果可以接受其他退出代码,请使用这个奇怪的技巧来检查多个值,而不必将 $LASTEXITCODE
的多个 eval 链接在一起:
# I use this often when checking the result of MSIEXEC
if ( $LASTEXITCODE -in @(0, 3010) )
....
基本上,这只是说检查$LASTEXITCODE
是否存在于-in
右侧的数组中。您可以使用-notin
来否定评估。 -contains/-notcontains
运算符也是一个选项,您只需将 -in/-notin
的操作数反转即可。
经过一些额外的测试,并模糊地回忆起几年前工作时的这个问题,我相信jfrog
正在向 STDERR 写所有可以解释你的行为的东西(你可以用$var = jfrog blah blah blah
进行测试,看看$var
包含输出,或者如果它仍然被写入控制台)。
如果您想缓解这种情况,例如,如果您想捕获输出,请将错误流(基本上是 STDERR)重定向到成功流(基本上是 STDOUT):
$output = jfrog blah blah blah 2>&1
但是,我仍然会依靠检查 $LASTEXITCODE
而不是输出或 $?
来检查失败。检查输出会导致此问题中出现类似问题,检查 $?
需要您注意将来对代码的更新,因为您很容易在执行和结果评估之间滑过另一个命令。
I have a comprehensive answer here 在不同的输出流和一般重定向上,您可能会觉得有用。
【讨论】:
可能是 JFrog 的错误。我会用他们记录一个问题,然后重写我的代码以使用 $LASTEXITCODE。 我的立场是正确的,STDERR 确实进入错误流。我也刚刚用ping
做了一个快速测试,两次都正确。所以我在这里想的是 jfrog 正在将所有内容输出到 STDERR。实际上,我模糊地回忆起几年前关于这件事的一些事情,但我的团队不管理 Artifactory,所以我不记得所有细节。
在控制台/终端的本地调用中,stderr 行被明智地直接传递到显示器(但在其他 PowerShell 主机中,特别是在远程和后台作业以及 SDK 中,它们确实去了通过 PowerShell 的错误流),因为它们不能被假定为 errors 。使用2>
重定向,您可以选择性地重定向标准错误输出。在 PowerShell 7.2 之前,这 - 不幸的是 - 需要通过 PowerShell 的错误流路由 stderr 输出,这会在$ErrorActionPreference = 'Stop'
生效时产生意想不到的副作用。
Quibble:“任何输出计算结果为 $true”显然不适用于 0
和 $false
等输出,甚至 , 0
或 , $false
(单元素集合是展开)。以上是关于为啥 PowerShell 不处理这个 $?正确退出代码?的主要内容,如果未能解决你的问题,请参考以下文章
为啥以下 Hello World 程序在 PowerShell 上没有显示任何输出?相同的程序在 CMD 上显示正确的输出