捕获程序stdout和stderr以分隔变量

dus*_*usz 40 windows powershell command-line stdout stderr

是否有可能在一次运行中将stdout从外部程序重定向到外部程序的变量和stderr到另一个变量?

例如:

$global:ERRORS = @();
$global:PROGERR = @();

function test() {
    # Can we redirect errors to $PROGERR here, leaving stdout for $OUTPUT?
    $OUTPUT = (& myprogram.exe 'argv[0]', 'argv[1]');

    if ( $OUTPUT | select-string -Pattern "foo" ) {
        # do stuff
    } else {
        $global:ERRORS += "test(): oh noes! 'foo' missing!";
    }
}

test;
if ( @($global:ERRORS).length -gt 0 ) {
    Write-Host "Script specific error occurred";
    foreach ( $err in $global:ERRORS ) {
        $host.ui.WriteErrorLine("err: $err");
    }
} else {
    Write-Host "Script ran fine!";
}

if ( @($global:PROGERR).length -gt 0 ) {
    # do stuff
} else {
    Write-Host "External program ran fine!";
}
Run Code Online (Sandbox Code Playgroud)

一个沉闷的例子,但我想知道这是否可能?

Aar*_*ltz 39

一种选择是将stdout和stderr的输出组合成单个流,然后过滤.

来自stdout的数据将是字符串,而stderr则生成System.Management.Automation.ErrorRecord对象.

$allOutput = & myprogram.exe 2>&1
$stderr = $allOutput | ?{ $_ -is [System.Management.Automation.ErrorRecord] }
$stdout = $allOutput | ?{ $_ -isnot [System.Management.Automation.ErrorRecord] }
Run Code Online (Sandbox Code Playgroud)

  • 或者甚至更好,用`&myprogram.exe 2>&1 |替换第一行 tee -Variable allOutput`.这样你就可以免费打印输出,甚至在stdout和stderr交错时保持顺序(其他答案都没有给出).这也不会通过任何在性能和最小化可能失败的事物方面取胜的文件. (9认同)
  • 结合@ OhadSchneider的方法捕获变量中的输出而不输出它:`[Void](&myprog.exe 2>&1 | tee -Variable allOutput)`然后`$ stdout = $ allOutput | ?{$ _ -isnot [System.Management.Automation.ErrorRecord]}`. (3认同)

Kei*_*ill 16

最简单的方法是使用stderr输出的文件,例如:

$output = & myprogram.exe 'argv[0]', 'argv[1]' 2>stderr.txt
$err = get-content stderr.txt
if ($LastExitCode -ne 0) { ... handle error ... }
Run Code Online (Sandbox Code Playgroud)

我还会使用$ LastExitCode来检查来自本机控制台的错误.

  • 重要的是要指出(至少对于stderr而言),输出不是命令产生的内容,而是主机报告命令产生的内容,这会对来自其他背景的人产生震惊。 (2认同)
  • 由于 `stderr.txt` 可能只使用一次,因此 [`New-TemporaryFile`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/new-temporaryfile) 出现便利 (2认同)

小智 9

您应该使用带有-RedirectStandardError -RedirectStandardOutput选项的Start-Process.这篇文章有一个很好的例子,如何做到这一点(从下面的帖子中抽样):

$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "ping.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = "localhost"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p.WaitForExit()
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
Write-Host "stdout: $stdout"
Write-Host "stderr: $stderr"
Write-Host "exit code: " + $p.ExitCode
Run Code Online (Sandbox Code Playgroud)

  • 这可以使一个不会异步写入stderr和stdout的应用程序死锁. (9认同)
  • 没关系,正如建议[这里](http://stackoverflow.com/a/11549817/1127485)``WaitForExit()`调用应该在*ReadToEnd()`调用之后*. (3认同)

Jam*_*Eby 5

这也是我用来重定向命令行的 stdout 和 stderr 的替代方法,同时在 PowerShell 执行期间仍显示输出:

$command = "myexecutable.exe my command line params"

Invoke-Expression $command -OutVariable output -ErrorVariable errors
Write-Host "STDOUT"
Write-Host $output
Write-Host "STDERR"
Write-Host $errors
Run Code Online (Sandbox Code Playgroud)

这只是补充已经给予的另一种可能性。

请记住,这可能并不总是有效,具体取决于脚本的调用方式。当从标准命令行而不是像这样的 PowerShell 命令行调用时,我遇到了 -OutVariable 和 -ErrorVariable 的问题:

PowerShell -File ".\FileName.ps1"
Run Code Online (Sandbox Code Playgroud)

在大多数情况下似乎有效的替代方法是:

$stdOutAndError = Invoke-Expression "$command 2>&1"
Run Code Online (Sandbox Code Playgroud)

不幸的是,您将在脚本执行期间丢失命令行的输出,并且必须Write-Host $stdOutAndError在命令返回后才能使其成为“记录的一部分”(就像 Jenkins 批处理文件运行的一部分)。不幸的是,它没有将 stdout 和 stderr 分开。