$ LastExitCode = 0,但在PowerShell中$?=假。 重定向错误输出到标准

2019-05-14 08:48发布

为什么PowerShell的显示在下面的第二个例子中,令人惊讶的行为?

首先,理智的行为的一个例子:

PS C:\> & cmd /c "echo Hello from standard error 1>&2"; echo "`$LastExitCode=$LastExitCode and `$?=$?"
Hello from standard error
$LastExitCode=0 and $?=True

没有惊喜。 我打印消息以标准误差(使用cmdecho )。 我检查变量$?$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在其中运行它可能是或不是炸毁取决于]

Answer 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


Answer 2:

此错误是PowerShell的处方性设计的错误处理的不可预见的后果,所以最有可能永远不会被固定。 如果你的脚本与其他PowerShell脚本只是扮演,你是安全的。 但是,如果你的脚本,以阔大的世界应用程序交互,这个错误可能会咬人。

PS> nslookup microsoft.com 2>&1 ; echo $?

False

疑难杂症! 尽管如此,一些痛苦的刮擦后,你永远也不会忘记的教训。

使用($LastExitCode -eq 0)而不是$?



Answer 3:

(注:这主要是炒作,我在PowerShell中很少使用许多本机命令和其他人可能知道更多关于PowerShell的内部比我)

我猜你发现在PowerShell控制台主机的差异。

  1. 如果PowerShell中拿起东西在标准错误流将承担错误,并抛出一个NativeCommandError
  2. 如果监控标准错误流PowerShell中只能挑选这件事。
  3. PowerShell ISE中可以监控它,因为它是没有控制台应用程序,因此本机控制台应用程序没有控制台来写。 这就是为什么在PowerShell ISE中失败而不管的2>&1重定向操作符。
  4. 如果使用控制台主机监测标准错误流2>&1重定向操作,因为在标准错误流输出必须被重定向,从而读出。

在这里,我的猜测是,控制台PowerShell主机是懒惰和公正手机控制台命令控制台,如果它并不需要做对他们输出的任何处理。

我真的相信这是一个错误,因为PowerShell的行为有所取决于主机应用程序。



Answer 4:

对我来说,与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