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

时间:2014-06-14 16:44:01

标签: 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!";
}

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

7 个答案:

答案 0 :(得分:32)

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

答案 1 :(得分:10)

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

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

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

答案 2 :(得分:8)

您应该使用带有-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

答案 3 :(得分:2)

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

PowerShell -File ".\FileName.ps1"

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

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

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

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

Write-Host $stdOutAndError

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

答案 4 :(得分:0)

另外,保留格式

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");

答案 5 :(得分:0)

起初,这个答案是在 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 :(得分:0)

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

然后使用 James 的部分答案得到 $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