【问题标题】:Capture program stdout and stderr to separate variables捕获程序 stdout 和 stderr 以分隔变量
【发布时间】:2014-08-05 00:04:17
【问题描述】:

是否可以在一次运行中将标准输出从外部程序重定向到一个变量,并将标准错误从外部程序重定向到另一个变量?

例如:

$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!";
}

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

【问题讨论】:

  • 您可以使用 Start-Process 运行 myprogram.exe as described here。它分别捕获 STDOUT 和 STDERR。

标签: windows powershell command-line stdout stderr


【解决方案1】:

一种选择是将 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] }

【讨论】:

  • 或者更好的是,将第一行替换为& myprogram.exe 2>&1 | tee -Variable allOutput。这样你就可以免费打印输出,甚至在 stdout 和 stderr 交错时保持顺序(其他答案都没有给出)。这也不会遍历任何文件,这在性能和最小化可能失败的事情方面是一个胜利。
  • 将@OhadSchneider 的方法与在不输出变量的情况下捕获输出相结合:[Void] (& myprog.exe 2>&1 | tee -Variable allOutput) 然后$stdout = $allOutput | ?{ $_ -isnot [System.Management.Automation.ErrorRecord] }
【解决方案2】:

最简单的方法是使用一个文件作为标准错误输出,例如:

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

我还会使用 $LastExitCode 来检查本地控制台 EXE 文件中的错误。

【讨论】:

  • 重要的是要指出(至少对于 stderr),输出不是命令产生的内容,而是主机对命令产生的内容的报告,这会令人震惊对于那些来自其他背景的人。
  • 由于stderr.txt 可能只使用一次,New-TemporaryFile 就派上用场了
【解决方案3】:

您应该使用带有 -RedirectStandardError -RedirectStandardOutput 选项的 Start-Process。这个other post 有一个很好的例子来说明如何做到这一点(从下面的帖子中取样):

$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

【讨论】:

  • 这会导致无法异步写入 stderr 和 stdout 的应用程序死锁。
  • @johnnycrash 有什么建议可以解决这个问题吗?
  • 没关系,正如here 建议的那样,WaitForExit() 调用应该在ReadToEnd() 调用之后进行。
【解决方案4】:

这也是我用来重定向命令行的 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

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

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

PowerShell -File ".\FileName.ps1"

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

$stdOutAndError = Invoke-Expression "$command 2>&1"

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

【讨论】:

    【解决方案5】:

    起初,这个答案是在another question下发布的。

    两个问题差不多,但是这里的关注度更高,所以我这里再发一个,或许可以为更多人提供解决方案。


    使用Where-Object(别名是符号?)是一种很明显的方法,但是有点太麻烦了。它需要很多代码。

    这样的话,不仅会花费更长的时间,而且会增加出错的概率。

    其实,PowerShell中有一种更简洁的方法可以将不同的流分离到不同的变量中(我偶然想到的)。

    # First, declare a method that outputs both streams at the same time.
    function thisFunc {
        [cmdletbinding()]
        param()
        Write-Output 'Output'
        Write-Verbose 'Verbose'
    }
    # The separation is done in a single statement.Our goal has been achieved.
    $VerboseStream = (thisFunc -Verbose | Tee-Object -Variable 'String' | Out-Null) 4>&1
    

    然后我们验证这两个变量的内容

    $VerboseStream.getType().FullName
    $String.getType().FullName
    

    控制台上应显示以下信息:

    PS> System.Management.Automation.VerboseRecord
    System.String
    

    '4>&1'表示将verboseStream重定向到成功流,然后可以保存到一个变量中,当然你可以把这个数字改成2到5之间的任意数字。

    如果你觉得我的方法还不错,请点击鼠标为我投票,非常感谢。

    【讨论】:

      【解决方案6】:

      单独保留格式

      cls
      function GetAnsVal {
          param([Parameter(Mandatory=$true, ValueFromPipeline=$true)][System.Object[]][AllowEmptyString()]$Output,
                [Parameter(Mandatory=$false, ValueFromPipeline=$true)][System.String]$firstEncNew="UTF-8",
                [Parameter(Mandatory=$false, ValueFromPipeline=$true)][System.String]$secondEncNew="CP866"
          )
          function ConvertTo-Encoding ([string]$From, [string]$To){#"UTF-8" "CP866" "ASCII" "windows-1251"
              Begin{
                  $encFrom = [System.Text.Encoding]::GetEncoding($from)
                  $encTo = [System.Text.Encoding]::GetEncoding($to)
              }
              Process{
                  $Text=($_).ToString()
                  $bytes = $encTo.GetBytes($Text)
                  $bytes = [System.Text.Encoding]::Convert($encFrom, $encTo, $bytes)
                  $encTo.GetString($bytes)
              }
          }
          $all = New-Object System.Collections.Generic.List[System.Object];
          $exception = New-Object System.Collections.Generic.List[System.Object];
          $stderr = New-Object System.Collections.Generic.List[System.Object];
          $stdout = New-Object System.Collections.Generic.List[System.Object]
          $i = 0;$Output | % {
              if ($_ -ne $null){
                  if ($_.GetType().FullName -ne 'System.Management.Automation.ErrorRecord'){
                      if ($_.Exception.message -ne $null){$Temp=$_.Exception.message | ConvertTo-Encoding $firstEncNew $secondEncNew;$all.Add($Temp);$exception.Add($Temp)}
                      elseif ($_ -ne $null){$Temp=$_ | ConvertTo-Encoding $firstEncNew $secondEncNew;$all.Add($Temp);$stdout.Add($Temp)}
                  } else {
                      #if (MyNonTerminatingError.Exception is AccessDeniedException)
                      $Temp=$_.Exception.message | ConvertTo-Encoding $firstEncNew $secondEncNew;
                      $all.Add($Temp);$stderr.Add($Temp)
                  }   
               }
          $i++
          }
          [hashtable]$return = @{}
          $return.Meta0=$all;$return.Meta1=$exception;$return.Meta2=$stderr;$return.Meta3=$stdout;
          return $return
      }
      Add-Type -AssemblyName System.Windows.Forms;
      & C:\Windows\System32\curl.exe 'api.ipify.org/?format=plain' 2>&1 | set-variable Output;
      $r = & GetAnsVal $Output
      $Meta2=""
      foreach ($el in $r.Meta2){
          $Meta2+=$el
      }
      $Meta2=($Meta2 -split "[`r`n]") -join "`n"
      $Meta2=($Meta2 -split "[`n]{2,}") -join "`n"
      [Console]::Write("stderr:`n");
      [Console]::Write($Meta2);
      [Console]::Write("`n");
      $Meta3=""
      foreach ($el in $r.Meta3){
          $Meta3+=$el
      }
      $Meta3=($Meta3 -split "[`r`n]") -join "`n"
      $Meta3=($Meta3 -split "[`n]{2,}") -join "`n"
      [Console]::Write("stdout:`n");
      [Console]::Write($Meta3);
      [Console]::Write("`n");
      

      【讨论】:

        【解决方案7】:

        如果您想从 PowerShell 脚本中获取任何信息并传递函数名,然后传递任何参数,您可以使用 dot sourcing 调用函数名及其参数。

        然后使用詹姆斯回答的一部分得到$output$errors

        .ps1 文件名为 W:\Path With Spaces\Get-Something.ps1,其中有一个名为 Get-It 的函数和一个参数 FilePath

        两个路径都用引号括起来,以防止路径中的空格破坏命令。

        $command = '. "C:\Path Spaces\Get-Something.ps1"; Get-It -FilePath "W:\Apps\settings.json"'
        
        Invoke-Expression $command -OutVariable output -ErrorVariable errors | Out-Null
        
        # This will get its output.
        $output
        
        # This will output the errors.
        $errors
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-09-02
          • 2012-06-17
          • 2011-04-07
          • 2018-10-31
          • 2020-06-23
          • 1970-01-01
          • 2012-02-29
          • 2013-01-12
          相关资源
          最近更新 更多