为什么PowerShell会在下面的第二个示例中显示令人惊讶的行为?
首先,一个理智行为的例子:
PS C:\> & cmd /c "echo Hello from standard error 1>&2"; echo "`$LastExitCode=$LastExitCode and `$?=$?"
Hello from standard error
$LastExitCode=0 and $?=True
没有惊喜。我将消息打印为标准错误(使用cmd
的{{1}})。我检查变量echo
和$?
。正如预期的那样,它们分别等于True和0。
但是,如果我要求PowerShell通过第一个命令将标准错误重定向到标准输出,我会得到一个NativeCommandError:
$LastExitCode
我的第一个问题,为什么NativeCommandError?
其次,当PS C:\> & cmd /c "echo Hello from standard error 1>&2" 2>&1; echo "`$LastExitCode=$LastExitCode and `$?=$?"
cmd.exe : Hello from standard error
At line:1 char:4
+ cmd <<<< /c "echo Hello from standard error 1>&2" 2>&1; echo "`$LastExitCode=$LastExitCode and `$?=$?"
+ CategoryInfo : NotSpecified: (Hello from standard error :String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
$LastExitCode=0 and $?=False
成功运行且$?
为0时,为什么cmd
为假? PowerShell的文档about automatic variables未明确定义$LastExitCode
。当且仅当$?
为0时,我总是认为它是真的,但我的例子与之相矛盾。
以下是我在现实世界(简化)中遇到此行为的方式。真的是FUBAR。我从另一个调用了一个PowerShell脚本。内部脚本:
$LastExitCode
简单地将其作为cmd /c "echo Hello from standard error 1>&2"
if (! $?)
{
echo "Job failed. Sending email.."
exit 1
}
# Do something else
运行,它运行正常,并且不会发送任何电子邮件。但是,我从另一个PowerShell脚本调用它,记录到文件.\job.ps1
。在这种情况下,会发送一封电子邮件!使用错误流在脚本外部执行的操作会影响脚本的内部行为。 观察现象会改变结果。这就像量子物理而不是脚本!
[有趣的是:.\job.ps1 2>&1 > log.txt
可能会或不会爆炸,具体取决于您运行它的位置]
答案 0 :(得分:65)
(我正在使用PowerShell v2。)
“$?
”变量记录在about_Automatic_Variables
:
$? Contains the execution status of the last operation
这是指最近的PowerShell操作,而不是最后一个外部命令,这是您在$LastExitCode
中获得的。
在您的示例中,$LastExitCode
为0,因为上一个外部命令是cmd
,它在回显某些文本时成功。但是2>&1
导致将stderr
的消息转换为输出流中的错误记录,这告诉PowerShell在上一次操作期间出现错误,导致{{1成为$?
。
为了说明这一点,请考虑一下:
> java -jar foo; $?; $LastExitCode Unable to access jarfile foo False 1
False
为1,因为这是java.exe的退出代码。 $LastExitCode
是假的,因为shell最后一次失败了。
但如果我所做的只是切换它们:
> java -jar foo; $LastExitCode; $? Unable to access jarfile foo 1 True
...然后$?
为True,因为shell做的最后一件事是向主机打印$?
,这是成功的。
最后:
> &{ java -jar foo }; $?; $LastExitCode Unable to access jarfile foo True 1
...这似乎有点反直觉,但$LastExitCode
现在是 True ,因为脚本块的执行成功,即使在其中运行的命令不是。
返回$?
重定向....导致错误记录进入输出流,这就是2>&1
的长卷blob。 shell正在转储整个错误记录。
当你想要做的就是将NativeCommandError
和 stderr
组合在一起时,这可能会特别烦人,因此它们可以组合在一个日志文件中。谁想要PowerShell对接他们的日志文件???如果我stdout
,则ant build 2>&1 >build.log
的任何错误都会增加PowerShell的 nosy $ 0.02,而不是在我的日志文件中收到干净的错误消息。
但是,输出流不是 text 流!重定向只是对象管道的另一种语法。错误记录是对象,因此您需要做的就是在重定向之前将该流上的对象转换为 strings :
自:
> cmd /c "echo Hello from standard error 1>&2" 2>&1 cmd.exe : Hello from standard error At line:1 char:4 + cmd &2" 2>&1 + CategoryInfo : NotSpecified: (Hello from standard error :String) [], RemoteException + FullyQualifiedErrorId : NativeCommandError
要:
> cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" } Hello from standard error
...并重定向到文件:
> cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" } | tee out.txt Hello from standard error
......或只是:
> cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" } >out.txt
答案 1 :(得分:16)
此错误是PowerShell针对错误处理的规范性设计无法预料的结果,因此很可能永远不会修复。如果您的脚本仅与其他PowerShell脚本一起播放,那么您就是安全的。但是,如果您的脚本与来自广阔世界的应用程序进行交互,则此错误可能会受到影响。
PS> nslookup microsoft.com 2>&1 ; echo $?
False
疑难杂症!仍然,经过一些痛苦的刮擦,你永远不会忘记这一课。
($LastExitCode -eq 0)
代替$?
答案 2 :(得分:10)
(注意:这主要是推测;我很少在PowerShell中使用很多本机命令,其他人可能比我更了解PowerShell内部结构)
我猜您在PowerShell控制台主机中发现了差异。
NativeCommandError
。2>&1
重定向运算符如何都会失败。2>&1
重定向操作符,控制台主机将监视标准错误流,因为标准错误流上的输出必须重定向并因此被读取。我的猜测是,控制台PowerShell主机是懒惰的,如果不需要对其输出进行任何处理,只需将本机控制台命令控制台。
我真的相信这是一个错误,因为PowerShell的行为会有所不同,具体取决于主机应用程序。
答案 3 :(得分:1)
有一个new experimental feature可以启用“理智”的行为:
onclick
不幸的是,此功能不能仅在当前范围内启用,它将在整个用户帐户(所有参数为None
的用户)上启用,并且需要启动新的PS会话才能生效。
现在此示例代码...
fn_convertLanguage('kor')
fn_convertLanguage('eng')
fn_convertLanguage('chn')
fn_convertLanguage('rus')
fn_convertLanguage('spn')
fn_convertLanguage('jpn')
GotoLogin()
register()
evalSearch()
...
...输出期望的结果:
Enable-ExperimentalFeature PSNotApplyErrorActionToStderr
答案 4 :(得分:0)
对我来说,这是ErrorActionPreference的一个问题。 当从ISE运行时,我在第一行设置了$ ErrorActionPreference =“Stop”,并且拦截了所有事件,并将*&gt;&amp; 1作为参数添加到呼叫中。
所以首先我有这条线:
& $exe $parameters *>&1
就像我说的那样不起作用,因为我之前在文件中有$ ErrorActionPreference =“Stop”(或者可以在用户启动脚本的配置文件中全局设置)。
所以我试图将它包装在Invoke-Expression中以强制ErrorAction:
Invoke-Expression -Command "& `"$exe`" $parameters *>&1" -ErrorAction Continue
这也不起作用。
所以我不得不使用临时覆盖的ErrorActionPreference来回避攻击:
$old_error_action_preference = $ErrorActionPreference
try
{
$ErrorActionPreference = "Continue"
& $exe $parameters *>&1
}
finally
{
$ErrorActionPreference = $old_error_action_preference
}
这对我有用。
我把它包装成一个函数:
<#
.SYNOPSIS
Executes native executable in specified directory (if specified)
and optionally overriding global $ErrorActionPreference.
#>
function Start-NativeExecutable
{
[CmdletBinding(SupportsShouldProcess = $true)]
Param
(
[Parameter (Mandatory = $true, Position = 0, ValueFromPipelinebyPropertyName=$True)]
[ValidateNotNullOrEmpty()]
[string] $Path,
[Parameter (Mandatory = $false, Position = 1, ValueFromPipelinebyPropertyName=$True)]
[string] $Parameters,
[Parameter (Mandatory = $false, Position = 2, ValueFromPipelinebyPropertyName=$True)]
[string] $WorkingDirectory,
[Parameter (Mandatory = $false, Position = 3, ValueFromPipelinebyPropertyName=$True)]
[string] $GlobalErrorActionPreference,
[Parameter (Mandatory = $false, Position = 4, ValueFromPipelinebyPropertyName=$True)]
[switch] $RedirectAllOutput
)
if ($WorkingDirectory)
{
$old_work_dir = Resolve-Path .
cd $WorkingDirectory
}
if ($GlobalErrorActionPreference)
{
$old_error_action_preference = $ErrorActionPreference
$ErrorActionPreference = $GlobalErrorActionPreference
}
try
{
Write-Verbose "& $Path $Parameters"
if ($RedirectAllOutput)
{ & $Path $Parameters *>&1 }
else
{ & $Path $Parameters }
}
finally
{
if ($WorkingDirectory)
{ cd $old_work_dir }
if ($GlobalErrorActionPreference)
{ $ErrorActionPreference = $old_error_action_preference }
}
}
答案 5 :(得分:0)
自v7.0起的问题的摘要:
PowerShell引擎在将2>
重定向应用于外部程序调用方面仍然存在错误:
根本原因是使用2>
导致stderr(标准错误)输出通过PowerShell的错误流路由 (请参阅about_Redirection ),则有以下不良后果:
如果$ErrorActionPreference = 'Stop'
生效,则使用2>
会意外触发脚本终止错误,即中止 (甚至采用2>$null
的形式,其目的显然是忽略 stderr行)。参见this GitHub issue。
$ErrorActionPreference = 'Continue'
由于2>
当前接触错误流$?
,因此,如果发出了至少一条stderr行,则自动将成功状态变量始终设置为$False
,然后不再反映命令的真实成功状态。参见this GitHub issue。
$LASTEXITCODE -eq 0
来测试成功。对于2>
,stderr行意外地记录在自动$Error
变量(该变量记录会话中发生的所有错误的变量)中,即使您使用{{ 1}}。参见this GitHub issue。
2>$null
逐一删除它们的记录,没有任何记录。通常,不幸的是,某些PowerShell主机默认情况下 通过PowerShell的错误流将来自外部程序的stderr输出路由,即将其作为错误处理 em>输出,这是不恰当的,因为许多外部程序也将stderr用作状态信息,或更普遍地,它用于任何内容就是不是数据($Error.RemoveAt()
是一个很好的例子):不能假定每条stderr行都代表一个错误,并且stderr输出的存在确实< em> not 暗示失败。
受影响的主机:
obsolescent Windows PowerShell ISE和其他可能的,较旧的基于GUI的IDE 不是Visual Studio代码。
通过PowerShell remoting或在background job 中执行外部程序时(这两种调用机制共享相同的基础结构,并使用git
主机PowerShell附带)。
在非远程,非后台调用中表现符合预期的主机(它们将stderr行传递到显示器并正常打印):
终端(控制台),包括即将推出的Windows Terminal。
Visual Studio Code 和PowerShell extension;该跨平台编辑器(IDE)旨在取代Windows PowerShell ISE。
this GitHub issue中讨论了主机之间的不一致问题。