redirect - PowerShell - 将可执行文件的 stderr 重定向到文件或变量,但仍将 stdout 转到控制台

标签 redirect powershell stream stderr powershell-3.0

我正在编写一个脚本来从 GitHub 下载多个存储库。这是下载存储库的命令:

git clone "$RepositoryUrl" "$localRepoDirectory"

当我运行此命令时,它会在我想要显示的控制台窗口中显示一些不错的进度信息。

问题是我还希望能够检测下载时是否发生任何错误。我发现this post that talks about redirecting the various streams ,所以我尝试了:

(git clone "$RepositoryUrl" "$localRepoDirectory") 2> $errorLogFilePath

这会将所有错误从 stderr 传送到我的文件,但不再在控制台中显示良好的进度信息。

我可以像这样使用 Tee-Object:

(git clone "$RepositoryUrl" "$localRepoDirectory") | Tee-Object -FilePath $errorLogFilePath

我仍然得到了很好的进度输出,但是这将 stdout 传输到文件,而不是 stderr;我只关心检测错误。

有没有一种方法可以存储文件或(最好)变量中发生的任何错误,同时仍然将进度信息通过管道传送到控制台窗口?我有一种感觉,答案可能就在 redirecting various streams into other streams as discusses in this post ,但我不太确定。

========更新=======

我不确定 git.exe 是否与典型的可执行文件不同,但我做了更多测试,以下是我发现的内容:

$output = (git clone "$RepositoryUrl" "$localRepoDirectory")

$output 始终包含文本“克隆到'[localRepoDirectory]'...”,无论命令成功完成还是产生错误。此外,执行此操作时,进度信息仍会写入控制台。这让我认为进度信息不是通过 stdout 写入的,而是通过其他流写入的?

如果发生错误,错误将写入控制台,但前景色为通常的白色,而不是典型的错误红色和警告黄色。当从 cmdlet 函数内调用此函数并且命令失败并出现错误时,错误不会通过函数的 -ErrorVariable (或 -WarningVariable)参数返回(但是,如果我执行自己的 Write-Error 操作,则会通过 -ErrorVariable 返回) )。这让我认为 git.exe 不会写入 stderr,但是当我们这样做时:

(git clone "$RepositoryUrl" "$localRepoDirectory") 2> $errorLogFilePath

错误消息被写入文件,所以这让我认为它确实写入了 stderr。所以现在我很困惑......

========更新2 =======

因此,在拜伦的帮助下,我使用新流程尝试了更多解决方案,但仍然无法得到我想要的。当使用新进程时,我从未将良好的进度写入控制台。

我尝试过的三种新方法都使用了这段共同的代码:

$process = New-Object System.Diagnostics.Process
$process.StartInfo.Arguments = "clone ""$RepositoryUrl"" ""$localRepoDirectory"""
$process.StartInfo.UseShellExecute = $false
$process.StartInfo.RedirectStandardOutput = $true
$process.StartInfo.RedirectStandardError = $true
$process.StartInfo.CreateNoWindow = $true
$process.StartInfo.WorkingDirectory = $WORKING_DIRECTORY
$process.StartInfo.FileName = "git"

方法 1 - 在新进程中运行并随后读取输出:

$process.Start()
$process.WaitForExit()
Write-Host Output - $process.StandardOutput.ReadToEnd()    
Write-Host Errors - $process.StandardError.ReadToEnd()

方法2 - 同步获取输出:

$process.Start()
while (!$process.HasExited)
{
    Write-Host Output - $process.StandardOutput.ReadToEnd()
    Write-Host Error Output - $process.StandardError.ReadToEnd()

    Start-Sleep -Seconds 1
}

尽管这看起来会在进程运行时写入输出,但在进程退出之前它不会写入任何内容。

方法 3 - 异步获取输出:

Register-ObjectEvent -InputObject $process -EventName "OutputDataReceived" -Action {Write-Host Output Data - $args[1].Data }
Register-ObjectEvent -InputObject $process -EventName "ErrorDataReceived" -Action { Write-Host Error Data - $args[1].Data }
$process.Start()
$process.BeginOutputReadLine()
$process.BeginErrorReadLine()
while (!$process.HasExited)
{
    Start-Sleep -Seconds 1
}

这确实在进程运行时输出数据,这很好,但它仍然没有显示好的进度信息:(

最佳答案

我想我已经有了你的答案。我使用 PowerShell 一段时间并创建了几个构建系统。抱歉,如果脚本有点长,但它确实有效。

$dir = <your dir>
$global:log = <your log file which must be in the global scope> # Not global = won't work

function Create-Process {
    $process = New-Object -TypeName System.Diagnostics.Process
    $process.StartInfo.CreateNoWindow = $false
    $process.StartInfo.RedirectStandardError = $true
    $process.StartInfo.UseShellExecute = $false
    return $process
}

function Terminate-Process {
    param([System.Diagnostics.Process]$process)

    $code = $process.ExitCode
    $process.Close()
    $process.Dispose()
    Remove-Variable process
    return $code
}

function Launch-Process {
    param([System.Diagnostics.Process]$process, [string]$log, [int]$timeout = 0)

    $errorjob = Register-ObjectEvent -InputObject $process -EventName ErrorDataReceived -SourceIdentifier Common.LaunchProcess.Error -action {
        if(-not [string]::IsNullOrEmpty($EventArgs.data)) {
            "ERROR - $($EventArgs.data)" | Out-File $log -Encoding ASCII -Append
            Write-Host "ERROR - $($EventArgs.data)"
        }
    }
    $outputjob = Register-ObjectEvent -InputObject $process -EventName OutputDataReceived -SourceIdentifier Common.LaunchProcess.Output -action {
        if(-not [string]::IsNullOrEmpty($EventArgs.data)) {
            "Out - $($EventArgs.data)" | Out-File $log -Encoding ASCII -Append
            Write-Host "Out - $($EventArgs.data)"
        }
    }

    if($errorjob -eq $null) {
        "ERROR - The error job is null" | Out-File $log -Encoding ASCII -Append
        Write-Host "ERROR - The error job is null"
    }

    if($outputjob -eq $null) {
        "ERROR - The output job is null" | Out-File $log -Encoding ASCII -Append
        Write-Host "ERROR - The output job is null"
    }

    $process.Start() 
    $process.BeginErrorReadLine()

    if($process.StartInfo.RedirectStandardOutput) {
        $process.BeginOutputReadLine() 
    }

    $ret = $null
    if($timeout -eq 0)
    {
        $process.WaitForExit()
        $ret = $true
    }
    else
    {
        if(-not($process.WaitForExit($timeout)))
        {
            Write-Host "ERROR - The process is not completed, after the specified timeout: $($timeout)"
            $ret = $false
        }
        else
        {
            $ret = $true
        }
    }

    # Cancel the event registrations
    Remove-Event * -ErrorAction SilentlyContinue
    Unregister-Event -SourceIdentifier Common.LaunchProcess.Error
    Unregister-Event -SourceIdentifier Common.LaunchProcess.Output
    Stop-Job $errorjob.Id
    Remove-Job $errorjob.Id
    Stop-Job $outputjob.Id
    Remove-Job $outputjob.Id

    $ret
}

$repo = <your repo>

$process = Create-Process
$process.StartInfo.RedirectStandardOutput = $true
$process.StartInfo.FileName = "git.exe"
$process.StartInfo.Arguments = "clone $($repo)"
$process.StartInfo.WorkingDirectory = $dir
Launch-Process $process $global:log
Terminate-Process $process

日志文件必须位于全局范围内,因为运行事件处理的例程不在脚本范围内。

我的日志文件示例:

输出 - 克隆到 ''... 错误 - check out 文件:22% (666/2971)
错误 - check out 文件:23% (684/2971)
错误 - checkout 文件:24% (714/2971)

关于redirect - PowerShell - 将可执行文件的 stderr 重定向到文件或变量,但仍将 stdout 转到控制台,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16467593/

相关文章:

apache 重写 POST 请求 SSL https mod_rewrite

asp.net - Response.Redirect - 使用异常进行流量控制?

jsf - 如何从 JSF 中的 managedBean 重新加载同一页面?

linux - Windows PowerShell 中的 grep 命令行来计算 IP 地址

powershell - 如何在 Get-CimInstance 中用 2-3 行将 TotalPhysicalMemory 转换为 GB?

node.js - ZeroMQ 推/拉和 Nodejs 读取流

.htaccess - 如何使用 .htaccess 重定向我的网页,以便将旧链接重定向到新链接?

regex - PowerShell正则表达式在换行符附近不匹配

go - 如何使用 libp2p 在 golang 中处理缓冲的读写流?

c++ - 如何使用 gRPC 在客户端和服务器之间双向发送和接收流元数据?