注意:
-
这个答案是关于当从那里调用 PowerShell 脚本时从外部世界的角度写入 stderr;虽然答案是从 Windows shell cmd.exe 的角度编写的,但当与 PowerShell Core 结合使用时,它同样适用于 Unix shell,例如 bash。 p>
-
相比之下,在内 Powershell,您应该使用Write-Error,如Keith Hill's answer 中所述。
-
遗憾的是,没有统一方法可以在内部 PowerShell 和从外部工作 -请参阅我的this answer 进行讨论。
添加到@Chris Sear's great answer:
虽然$host.ui.WriteErrorLine 应该适用于所有主机,当通过cmd.exe 调用时,它不会(默认)写入标准错误,例如从批处理文件。
[Console]::Error.WriteLine,相比之下,总是。
所以如果您想编写一个 PowerShell 脚本,在从 cmd.exe 调用时可以很好地输出流,请使用以下函数 Write-StdErr,它使用 [Console]::Error.WriteLine
在常规 PS / cmd.exe 主机(控制台窗口)中,$host.ui.WriteErrorLine 否则:
<#
.SYNOPSIS
Writes text to stderr when running in a regular console window,
to the host''s error stream otherwise.
.DESCRIPTION
Writing to true stderr allows you to write a well-behaved CLI
as a PS script that can be invoked from a batch file, for instance.
Note that PS by default sends ALL its streams to *stdout* when invoked from
cmd.exe.
This function acts similarly to Write-Host in that it simply calls
.ToString() on its input; to get the default output format, invoke
it via a pipeline and precede with Out-String.
#>
function Write-StdErr {
param ([PSObject] $InputObject)
$outFunc = if ($Host.Name -eq 'ConsoleHost') {
[Console]::Error.WriteLine
} else {
$host.ui.WriteErrorLine
}
if ($InputObject) {
[void] $outFunc.Invoke($InputObject.ToString())
} else {
[string[]] $lines = @()
$Input | % { $lines += $_.ToString() }
[void] $outFunc.Invoke($lines -join "`r`n")
}
}
可选背景信息:外部调用者如何看到 PowerShell CLI 的输出流:
在内部,PowerShell 比传统的输出流(stdout 和 stderr)更多,并且它们的数量随着时间的推移而增加(尝试以 Write-Warning "I'll go unheard." 3> $null 为例,并在 Get-Help about_Redirection 阅读更多内容。
在与外界交互时,PowerShell 必须将非传统输出流映射到 stdout 和 stderr。
然而,奇怪的是,当从cmd.exe 调用时,PowerShell 默认将所有 其流(包括Write-Host 和$host.ui.WriteErrorLine() 输出)发送到stdout ,尽管将 PowerShell 的错误流映射到 stderr 是合乎逻辑的选择。此行为从(至少)v2 开始生效,并且自 v5.1 起仍然适用(并且由于向后兼容性的原因可能不会更改 - 请参阅 GitHub issue #7989)。
您可以使用以下命令验证这一点,如果您从 cmd.exe 调用它:
powershell -noprofile -command "'out'; Write-Error 'err'; Write-Warning 'warn'; Write-Verbose -Verbose 'verbose'; $DebugPreference='Continue'; write-debug 'debug'; $InformationPreference='Continue'; Write-Information 'info'; Write-Host 'host'; $host.ui.WriteErrorLine('uierr'); [Console]::Error.WriteLine('cerr')" >NUL
该命令写入所有 PowerShell 输出流(当您在 PowerShell-v5 之前的版本上运行时,您将看到与 Write-Information 相关的附加错误消息,该错误消息是在 PowerShell v5 中引入的)并且具有 cmd.exe将仅标准输出重定向到NUL(即抑制标准输出;>NUL)。
除了cerr(来自[Console]::Error.WriteLine(),直接写入stderr)之外,您将看到没有输出-所有PowerShell的流都发送到stdout。
也许更奇怪的是,可以捕获 PowerShell 的错误流,但只能通过重定向:
如果您将上面的>NUL 更改为2>NUL,则只会抑制PowerShell 的错误流 和$host.ui.WriteErrorLine() 输出;当然,与任何重定向一样,您也可以将其发送到文件。
(如上所述,[Console]::Error.WriteLine()] 总是 输出到 stderr,无论后者是否被重定向。)
举一个更有针对性的例子(同样,从cmd.exe运行):
powershell -noprofile -command "'out'; Write-Error 'err'" 2>NUL
上面只输出out - Write-Error的输出被抑制了。
总结:
-
没有任何 (cmd.exe) 重定向或仅 stdout 重定向(>... 或1>...),PowerShell 发送所有 它的输出流到stdout。
-
通过 stderr 重定向 (2>...),PowerShell 有选择地 将其 错误流 发送到 stderr (不管标准输出是否也被重定向)。
-
作为推论,以下常见习语不按预期工作:
powershell ... >data-output.txt
正如人们所期望的那样,这不会在将 stderr 输出打印到终端时仅将 stdout 发送到文件 data-output.txt;相反,您必须使用
powershell ... >data-output.txt 2>err-output.tmp; type err-output.tmp >&2; del err-output.tmp
因此,PowerShell 知道cmd.exe 的重定向并有意地调整其行为。
(从 PowerShell 在 cmd.exe console 中生成 colored 输出,同时在输出重定向到文件时剥离颜色代码,这一点也很明显。)