我有困难弄清楚如何同时将内容回显到标准错误流和重定向可执行文件的错误流。
我来自Bourne shell和Korn shell背景,我通常会使用以下方法:
# Write to stderr
echo "Error Message!" >&2
# Redirect stderr to file
/do/error 2>/tmp/err.msg
我有困难弄清楚如何同时将内容回显到标准错误流和重定向可执行文件的错误流。
我来自Bourne shell和Korn shell背景,我通常会使用以下方法:
# Write to stderr
echo "Error Message!" >&2
# Redirect stderr to file
/do/error 2>/tmp/err.msg
使用Write-Error
将内容写入标准错误流(stderr),如果想要将标准错误流重定向到文件,请使用:
Write-Error "oops" 2> /temp/err.msg
或者 exe_that_writes_to_stderr.exe bogus_arg 2> /temp/err.msg
请注意,PowerShell以错误记录的形式编写错误。如果你想避免冗长的错误记录输出,你可以按如下方式自己编写错误信息:
PS> Write-Error "oops" -ev ev 2>$null
PS> $ev[0].exception
oops
-EV
是-ErrorVariable
的简称,任何错误都将存储在此参数命名的变量中。 除非我们将错误重定向到$null
,否则PowerShell仍将向控制台报告错误。
cmd >stdout.log 2>stderr.log
。请考虑能够输入内容并将其管道传输到stderr,目前看来似乎无法实现。 - Brett RyanWrite-Error "oops" -ev ev 2>&1 > $null
不会将任何内容重定向到标准错误输出。Write-Error "oops"
很好用,但你会卡在冗长的输出上。 - Ohad Schneiderstderr
输出,而且oops
实际上根本没有被重定向到stderr
(我尝试在外部执行它,结果oops
只出现在标准输出中)。 - Ohad Schneider$null
,但我仍然能在我的程序中看到"oops"被输出到了StdErr。 - Steven Vachon注意:
本回答讨论的是在从外部调用PowerShell脚本时从“外部世界”的角度写入stderr;尽管该答案是从Windows shell(cmd.exe)的角度编写的,但与PowerShell Core结合使用时,同样适用于Unix shell(如bash)。
相比之下,从Powershell内部,您应该使用Write-Error
,如Keith Hill的答案所述。
不幸的是,没有一种统一的方法可以同时从PowerShell内部和外部工作-请参见我的这个答案进行讨论。
虽然$host.ui.WriteErrorLine
应该在所有主机上都能工作,但如果通过批处理文件等方式从cmd.exe
调用,则默认情况下它不会将内容写入stderr。
相比之下,[Console]::Error.WriteLine
始终会将内容写入stderr。
因此,如果想要编写一个在从cmd.exe
调用时在输出流方面正常工作的PowerShell脚本,请使用以下函数Write-StdErr
,该函数在常规的PS/cmd.exe
主机(控制台窗口)中使用[Console]::Error.WriteLine
,
否则使用$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 拥有比传统的输出流(stdout 和 stderr)更多的输出流,并且它们的数量随着时间的推移而增加(尝试 Write-Warning "I'll go unheard." 3> $null
作为示例,并在 Get-Help about_Redirection
中进一步了解)。
与外部交互时,PowerShell 必须将非传统输出流映射到 stdout 和 stderr。
然而奇怪的是,默认情况下 PowerShell 将所有的输出流(包括 Write-Host
和 $host.ui.WriteErrorLine()
输出)发送到 stdout,即使将 PowerShell 的错误流映射到 stderr 是逻辑上的选择。这个行为从至少 v2 开始生效,到 v5.1 时仍然有效(可能出于向后兼容性的原因,不会改变 - 参见 GitHub 问题 #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 仅将 stdout 重定向到 NUL(即抑制 stdout 输出)。除了 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 (无论 stdout 是否也被重定向)。
因此,以下常见用法不会按预期工作:
powershell ... >data-output.txt
这不会像人们期望的那样只将 stdout 发送到文件 data-output.txt
,同时将 stderr 输出打印到终端;相反,您必须使用
powershell ... >data-output.txt 2>err-output.tmp; type err-output.tmp >&2; del err-output.tmp
由此可见,PowerShell 意识到 cmd.exe
的重定向并有意调整其行为。(这也可以从 PowerShell 在 cmd.exe
的 控制台 中生成 彩色 输出,而在将输出重定向到文件时剥离颜色代码的情况下看出。)
$Host.UI.WriteErrorLine
的输出发送到stdout而不是stderr,即使在pwsh shell中调用。 - Marc$Host.UI.WriteErrorLine
- 以及 Write-Error
- 默认情况下写入 stdout,但是你可以使用 2>
进行重定向,如答案中所述。要默认写入外部世界的 stderr,请使用 [Console]::Error.WriteLine()
,如答案中所推荐的那样,但代价是无法在 PowerShell 会话内捕获此类输出。 - mklement0-ErrorVariable
,而且必须使用 -ErrorVariable foo
- 也就是说,您不能使用 $
符号。 - mklement0./scriptlet.ps1 2>
没有捕获错误,而 run-cmdlet 2>
却可以。 - Marc你可能想要这个:
$host.ui.WriteErrorLine('I work in any PowerShell host')
你可能也会看到以下内容,但它假定你的PowerShell主机是一个控制台窗口/设备,因此我认为它的用处较小:
[Console]::Error.WriteLine('I will not work in the PowerShell ISE GUI')
WriteErrorLine
不是好的选择。查看文档(https://msdn.microsoft.com/en-us/library/system.management.automation.host.pshostuserinterface.writeerrorline%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396):*此方法将一行写入主机错误显示。* 换句话说,这是一个UI方法,有时按照您的期望工作并重定向到stderr,而有时(大多数情况下?)不会(请参阅mklement0的答案:https://dev59.com/52445IYBdhLWcg3wLXMK#15669365)。正如Keith Hill所说,Write-Error
是最佳选项。 - Ohad Schneider
Write-Error
会导致我的脚本终止,因为我使用了$ErrorActionPreference = "Stop"
,我认为这更有价值。 - Peter L