为什么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
的echo
)。 我检查变量$?
和$LastExitCode
。 他们分别等于真和0,符合市场预期。
然而,如果我问PowerShell来重定向标准误差在第一个命令的标准输出,我得到一个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
我的第一个问题,为什么NativeCommandError?
其次,为什么$?
假当cmd
成功运行,并且$LastExitCode
是0? PowerShell的文档有关自动变量没有明确界定$?
。 我一直认为这是真当且仅当$LastExitCode
是0,但我的例子是矛盾。
下面是在现实世界中我碰到这种行为怎么来的(简化)。 这真的是FUBAR。 我打电话从另外一个PowerShell脚本。 内部脚本:
cmd /c "echo Hello from standard error 1>&2"
if (! $?)
{
echo "Job failed. Sending email.."
exit 1
}
# Do something else
运行此只是作为.\job.ps1
,它工作正常,并没有发送电子邮件。 不过,我是从另一个PowerShell脚本调用它,记录到文件.\job.ps1 2>&1 > log.txt
。 在这种情况下,电子邮件被发送! 你做了错误流脚本之外什么影响脚本的内部行为。 观察现象变化的结果。 这感觉就像量子物理而不是脚本!
[有趣的是: .\job.ps1 2>&1
在其中运行它可能是或不是炸毁取决于]
(我使用的PowerShell V2)。
在' $?
'变量被记录在about_Automatic_Variables
:
$?
Contains the execution status of the last operation
这是指最近的PowerShell的操作,而不是最后的外部命令,这是你会得到什么$LastExitCode
。
在你的榜样, $LastExitCode
是0,因为最后一个外部命令cmd
,这是成功的呼应一些文本。 但2>&1
原因消息stderr
要转换为错误的输出流,它告诉PowerShell中的最后一个操作过程中出现错误,从而导致记录$?
是False
。
为了说明这多一点,考虑一下:
> java -jar foo; $?; $LastExitCode
Unable to access jarfile foo
False
1
$LastExitCode
是1,因为这是java.exe的退出代码。 $?
是假的,因为最后的事情外壳也失败了。
但是,如果我要做的就是围绕切换它们:
> java -jar foo; $LastExitCode; $?
Unable to access jarfile foo
1
True
...那么$?
是真的,因为壳做的最后一件事是打印$LastExitCode
到主机,这是成功的。
最后:
> &{ java -jar foo }; $?; $LastExitCode
Unable to access jarfile foo
True
1
...这似乎有点违反直觉的,但$?
现在是真的 ,因为脚本块的执行是成功的 ,即使命令来运行它里面是不是。
返回到2>&1
重定向....会导致一个错误记录输出流,这是什么让进去那个啰嗦的blob关于NativeCommandError
。 外壳采用倾倒整个错误记录。
这可能是特别恼人时,所有你需要做的就是管stderr
和 stdout
一起,使他们能够在一个日志文件或东西相结合。 谁愿意PowerShell的对接在其日志文件??? 如果我做ant build 2>&1 >build.log
,那么去任何错误stderr
有PowerShell的八卦 $ 0.02上涨了,而不是在我的日志文件中获取干净的错误消息。
但是,输出流是不是一个文本流! 重定向只是为对象管道的另一种语法。 错误记录对象,因此,所有你需要做的就是重新定向之前转换在那个流为字符串的对象:
从:
> 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
此错误是PowerShell的处方性设计的错误处理的不可预见的后果,所以最有可能永远不会被固定。 如果你的脚本与其他PowerShell脚本只是扮演,你是安全的。 但是,如果你的脚本,以阔大的世界应用程序交互,这个错误可能会咬人。
PS> nslookup microsoft.com 2>&1 ; echo $?
False
疑难杂症! 尽管如此,一些痛苦的刮擦后,你永远也不会忘记的教训。
使用($LastExitCode -eq 0)
而不是$?
(注:这主要是炒作,我在PowerShell中很少使用许多本机命令和其他人可能知道更多关于PowerShell的内部比我)
我猜你发现在PowerShell控制台主机的差异。
- 如果PowerShell中拿起东西在标准错误流将承担错误,并抛出一个
NativeCommandError
。 - 如果监控标准错误流PowerShell中只能挑选这件事。
- PowerShell ISE中可以监控它,因为它是没有控制台应用程序,因此本机控制台应用程序没有控制台来写。 这就是为什么在PowerShell ISE中失败而不管的
2>&1
重定向操作符。 - 如果使用控制台主机将监测标准错误流
2>&1
重定向操作,因为在标准错误流输出必须被重定向,从而读出。
在这里,我的猜测是,控制台PowerShell主机是懒惰和公正手机控制台命令控制台,如果它并不需要做对他们输出的任何处理。
我真的相信这是一个错误,因为PowerShell的行为有所取决于主机应用程序。
对我来说,与ErrorActionPreference的问题。 当从ISE运行我设置$ ErrorActionPreference在第一线=“停止”,那就是拦截一切事件与*>&1加入参数调用。
所以首先我有这样一行:
& $exe $parameters *>&1
这就像我说没有工作,因为我有$ ErrorActionPreference =“停止”先前在文件(或者它可以在全球范围内为用户配置文件设置启动脚本)。
所以,我试图把它包在调用-表达给力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 }
}
}
文章来源: $LastExitCode=0, but $?=False in PowerShell. Redirecting stderr to stdout gives NativeCommandError