为什么PowerShell会在stderr上删除消息?

时间:2015-11-21 01:19:52

标签: powershell error-handling ghdl

我使用PowerShell脚本来控制编译器的不同编译步骤(ghdl.exe)。

编译器有3种不同的输出格式:

  1. 无输出且无错误=> $ LastExitCode = 0
  2. 输出stderr(警告),但没有错误=> $ LastExitCode = 0
  3. 输出stderr(错误),也许警告=> $ LastExitCode!= 0
  4. 由于处理stderr和stdout接缝非常错误,我使用了StackOverflow帖子中提供的方法:PowerShell: Manage errors with Invoke-Expression

    以下是我添加消息着色的实现:

    function Format-NativeCommandStreams
    { param([Parameter(ValueFromPipeline=$true)]$InputObject)
    
      begin
      { $ErrorRecordFound = $false  }
    
      process
      { if (-not $InputObject)
        { Write-Host "Empty"  }
        elseif ($InputObject -is [System.Management.Automation.ErrorRecord])
        { $ErrorRecordFound  = $true
          $text = $InputObject.ToString()
          Write-Host $text -ForegroundColor  Gray
    
          $stdErr = $InputObject.TargetObject
          if ($stdErr)
          { #Write-Host ("err: type=" + $stdErr.GetType() + "  " + $stdErr)
            if ($stdErr.Contains("warning"))
            { Write-Host "WARNING: "  -NoNewline -ForegroundColor Yellow  }
            else
            { Write-Host "ERROR: "    -NoNewline -ForegroundColor Red      }
            Write-Host $stdErr
          }
        }
        else
        { $stdOut = $InputObject                
          if ($stdOut.Contains("warning"))
          { Write-Host "WARNING: "  -NoNewline -ForegroundColor Yellow  }
          else
          { Write-Host "ERROR: "    -NoNewline -ForegroundColor Red      }
          Write-Host $stdOut
        }
      }
    
      end
      { $ErrorRecordFound    }
    }
    

    用法:

    $Options = @(.....)
    $Expr = "ghdl.exe -a " + ($Options -join " ") + " " + $File + " 2>&1"
    $ret = Invoke-Expression $Expr | Format-NativeCommandStreams
    

    通常,编译器每行发出一条消息(错误或警告)。如下面的屏幕截图所示,一些消息最多被切断为8行。这就是为什么我的输出着色不能按预期工作的原因。更多的行被检测为错误(误报),因此我无法在日志中找到真正的错误。

    chopped messages(点击)

    示例:

    C:\Altera\15.0\quartus\eda\sim_lib\altera_mf.vhd:
    39963:
    53
    :
    warning:
    
    universal integer bound must be numeric literal or attribute
    
    C:\Altera\15.0\quartus\eda\sim_lib\altera_mf.vhd
    :41794:36:warning: universal integer bound must be numeric literal or attribute
    

    预期结果:

    C:\Altera\15.0\quartus\eda\sim_lib\altera_mf.vhd:39963:53:warning: universal integer bound must be numeric literal or attribute
    C:\Altera\15.0\quartus\eda\sim_lib\altera_mf.vhd:41794:36:warning: universal integer bound must be numeric literal or attribute
    

    据我所见,编译器(ghdl.exe)确实以完整行的形式发出消息。

    问题:

    • 为什么会这样?
    • 我可以解决这个问题吗?

3 个答案:

答案 0 :(得分:2)

解决方案

可执行文件stderr上的完整输出只是分散在System.Management.Automation.ErrorRecord类型的多个对象上。实际拆分似乎是非确定性的(*)。此外,部分字符串存储在属性Exception内,而不是TargetObject。只有第一个ErrorRecord具有非空TargetObject。也就是说,为什么包含字符串"warning"的输出的后续行未格式化为黄色和白色,如下所示:

:41794:36:warning: universal integer bound must be numeric literal or attribute

您的灰色输出来自每个toString()的{​​{1}}方法,该方法返回此记录的属性ErrorRecord的值。 因此必须将所有消息连接在一起,以便在格式化之前获得整个输出。这些消息中保留了换行符。

编辑 :( *)它取决于程序的写入/刷新调用与Powershell的读取调用的顺序。如果在我的测试程序中的每个Exception.Message之后添加fflush(stderr),则会有更多fprintf()个对象。除了第一个似乎是确定性的,它们中的一些包括2个输出线,其中一些包含3个。

我的测试平台

我没有使用GHDL,而是使用新的Visual Studio项目开始,并使用以下代码创建了一个控制台应用程序(HelloWorldEx)。它只是在ErrorRecord

上打印出很多编号的行
stderr

然后我编译了程序并在Powershell中执行它: (编辑:从我自己的脚本中删除调试代码)

#include "stdafx.h"
#include <stdio.h>

int _tmain(int argc, _TCHAR* argv[])
{
  // Print some warning messages on stderr
  for(int i=0; i<70; i++) {
    fprintf(stderr, "warning:%070d\n", i); // 80 bytes per line including CR+LF
  }
  return 0; // exit code is not relevant
}

这是脚本的输出。正如您所看到的,我的程序输出分为3 .\HelloWorldEx.exe 2>&1 | set-variable Output $i = 0 $Output | % { Write-Host ("--- " + $i + ": " + $_.GetType() + " ------------------------") Write-Host ($_ | Format-List -Force | Out-String) $i++ } (实际可能不同):

ErrorRecords

答案 1 :(得分:1)

你可以进行一些调试来解决这个问题。我建议从这样的事情开始:

ghdl.exe <whatever args you supply> 2>&1 | set-variable ghdlOutput
$i = 0
$ghdlOutput | % {write-host "$i `t: " $_.gettype() "`t" $_ ; $i++}

这将列出行号,输出行的类型以及输出的每个实时。您可能需要调整一些代码以使输出看起来不错。

从那里你可以看到编译器是否真的将错误分成多行。如果是,你可以尝试设计一个策略来确定哪些行是stdout,哪些是stderr。如果没有,那么你将有一些线索来调试上面的脚本。

或者可以打包整个方法并使用.NET system.diagnostics.process类并将stdout和stderr重定向为单独的流。使用带有ProcessStartInfo的Start方法。如果需要,您应该能够谷歌这样做的例子。

答案 2 :(得分:0)

似乎我以马丁·扎贝尔(Martin Zabel)为例解决了这个问题,结果证明该解决方案很平淡无奇。

事实是很长一段时间以来,我都无法从来电中得到字符`r`n。事实证明这很简单。

唯一需要做的就是用`n代替`r!

该解决方案将在控制台的任何宽度上正常工作,因为反向功能已被删除。

此外,这可能是解决将处理后的数据实时返回到控制台的问题的基础。唯一需要做的就是捕获传入的单个r或n以获取新的“可变字符串”,并根据任务将处理后的数据使用r或n发送回控制台。 >

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
$Meta0=""
foreach ($el in $r.Meta0){
    $Meta0+=$el
}
$Meta0=($Meta0 -split "[`r`n]") -join "`n"
$Meta0=($Meta0 -split "[`n]{2,}") -join "`n"
[Console]::Write($Meta0);
[Console]::Write("`n");