为啥 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$null0、空字符串和空数组计算为$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' 生效时产生意想不到的副作用。 Quib​​ble:“任何输出计算结果为 $true”显然不适用于 0$false 等输出,甚至 , 0, $false(单元素集合是展开)。

以上是关于为啥 PowerShell 不处理这个 $?正确退出代码?的主要内容,如果未能解决你的问题,请参考以下文章

为啥以下 Hello World 程序在 PowerShell 上没有显示任何输出?相同的程序在 CMD 上显示正确的输出

苹果为啥最近老闪退怎么办

使用过vmware 再开启wsl2闪退处理

为啥 Scala 偶尔会退回到 Java 对象?

至少我认为PowerShell没有正确处理参数?

为啥这个SQL Server DBA学习PowerShell