PowerShell: Capture the output from external process that writes to stderr in a variable

后端 未结 2 1282
闹比i
闹比i 2020-12-10 19:47

I need to capture the output of a external process into a variable (string), so I can do some processing on that.

The answer from here works nicely as long as the p

相关标签:
2条回答
  • 2020-12-10 20:31

    To complement manojlds' helpful answer with an overview:

    To briefly explain redirection expression 2>&1:
    2> redirects (>) PowerShell's error output stream, whose number is 2 (and which maps onto stderr) into (&) PowerShell's success output stream, whose number is 1 (and maps onto stdout).
    Run Get-Help about_Redirection to learn more.

    Capturing stdout and stderr output combined, as a merged stream:


    As a collection of output lines as strings, without the ability to tell which line came from what stream:

    Using the platform-native shell to perform the merging at the source makes PowerShell only see stdout output, which, as usual, it collects in an array of strings.

    In the following examples, the commands each create both stdout and stderr output.
    All commands use PSv3+ syntax for convenience and brevity.

    Windows example:

    # Collect combined output across both streams as an array of
    # strings, where each string represents and output line.
    # Note the selective quoting around & and 2>&1 to make sure they
    # are passed through to cmd.exe rather than PowerShell itself interpreting them.
    $allOutput = cmd /c ver '&' dir \nosuch '2>&1'
    

    Unix example (PowerShell Core):

    # sh, the Unix default shell, expects the entire command as a *single* argument.
    $allOutput = sh -c '{ date; ls /nosuch; } 2>&1'
    

    Note:

    • You do need to call the platform-native shell explicitly (cmd /c on Windows, sh -c on Unix), which makes this approach less portable.

    • You will not be able to tell from the resulting array of lines which line came from what stream.
      See below for how to make this distinction.


    As a mix of string lines (from stdout) and [System.Management.Automation.ErrorRecord] "lines" (from stderr):

    By using PowerShell's 2>&1 redirection, you also get a single collection of lines representing the merged stdout and stderr streams, but the stderr lines aren't captured as strings, but as [System.Management.Automation.ErrorRecord] instances.

    Caveat: A bug as of Windows PowerShell v5.1 / PowerShell Core v6.0.0-beta.4 results in unexpected behavior when you redirect PowerShell's error stream with 2> while $ErrorActionPreference = 'Stop' is in effect - see this GitHub issue.

    This gives you the flexibility to distinguish between stdout and stderr lines by examining the data type of each array element.
    On the flip side, you may have to convert the stderr lines to strings.

    In PSv6, the different data types are not obvious when you simply output the captured output, but you can tell by reflection:

    # Let PowerShell merge the streams with 2>&1, which captures
    # stdout lines as strings and stderr lines as [System.Management.Automation.ErrorRecord] 
    # instances.
    $allOutput = cmd /c ver '&' dir \nosuch 2>&1
    

    Inspect the result:

    PS> $allOutput | % GetType | % Name
    String
    String
    String
    String
    String
    String
    ErrorRecord
    

    As you can see, the last array element is an error record, which represent the single File Not Found stderr output line produced by the dir \nosuch command.

    Note: Up to PSv5.1, when you output the captured output to the console, the [System.Management.Automation.ErrorRecord] instances actually rendered in the same format as PowerShell errors, perhaps making it appear as if an error had occurred then.
    In PSv6, these error records print just their message, i.e., the contents of the original stderr line, and visually you can't tell that an error record rather than a string is being printed.

    To convert all captured output to strings:

    $allOutput = cmd /c ver '&' dir \nosuch 2>&1 | % ToString
    

    To filter out the stderr lines (and converting them to strings in the process):

    $allOutput = cmd /c ver '&' dir \nosuch 2>&1
    $stderrOnly = $allOutput | ? { $_ -is [System.Management.Automation.ErrorRecord] } | 
      % ToString
    

    Capturing stderr output separately:


    Using a temporary file:

    As of PSv5.1, the only direct way to capture stderr output in isolation is to use redirection 2> with a filename target; i.e., to capture stderr output - as text - in a file:

    $stderrFile = New-TemporaryFile # PSv5+; PSv4-: use [io.path]::GetTempFileName()
    $stdoutOutput = cmd /c ver '&' dir \nosuch 2>$stderrFile
    $stderrOutput = Get-Content $stdErrFile
    Remove-Item $stderrFile
    

    Clearly, this is cumbersome and also slower than in-memory operations.


    Using the PSv4+ .Where() collection operator:

    The (little-known) PSv4+ .Where() collection operator allows you to split a collection in two, based on whether the elements pass a Boolean test or not:

     # Merge the streams first, so they go to the success stream, then
     # split the objects in the merged stream by type.
     $stdoutOutput, $stderrOutput = (cmd /c ver '&' dir \nosuch 2>&1).Where({
       $_ -isnot [System.Management.Automation.ErrorRecord]
     }, 'Split')
    
     # Convert the collected [System.Management.Automation.ErrorRecord]
     # instances to strings.
     $stderrOutput = $stderrOutput | % ToString
    

    Potential future alternative:

    This GitHub issue proposes a new redirection syntax that would allow collecting stderr lines in a variable much more simply:

     # As of v5.1: WISHFUL THINKING
     $stdout = cmd /c ver '&' dir \nosuch 2>&stderr # collect stderr lines in var. $stderr
    
    0 讨论(0)
  • 2020-12-10 20:38

    Try this:

    $cmdOutput = svn info 2>&1
    
    0 讨论(0)
提交回复
热议问题