PowerShell - 将可执行文件的stderr重定向到文件或变量,但仍然有stdout转到控制台
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PowerShell - 将可执行文件的stderr重定向到文件或变量,但仍然有stdout转到控制台相关的知识,希望对你有一定的参考价值。
我正在写一个脚本从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始终包含文本“Cloning into'[localRepoDirectory]'...”,无论命令是成功完成还是产生错误。此外,执行此操作时,仍会将进度信息写入控制台。这让我认为进度信息不是通过stdout编写的,而是通过其他一些流编写的?
如果发生错误,则错误将写入控制台,但采用通常的白色前景色,不是典型的红色表示错误,黄色表示警告。当从cmdlet函数中调用此函数并且命令因错误而失败时,错误不会通过函数的-ErrorVariable(或-WarningVariable)参数返回(但是,如果我执行自己的Write-Error,它会通过-ErrorVariable返回)。这让我觉得git.exe没有写入stderr,但是当我们这样做时:
(git clone "$RepositoryUrl" "$localRepoDirectory") 2> $errorLogFilePath
错误消息被写入文件,这使我认为它写入stderr。所以现在我很困惑......
========更新2 =======
因此,在Byron的帮助下,我尝试了一些使用新流程的解决方案,但仍然无法获得我想要的东西。使用新进程时,我永远无法将好的进度写入控制台。
我尝试过的三种新方法都使用了这个共同的代码:
$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
日志文件必须位于全局范围内,因为运行事件处理的例程不在脚本范围内。
我的日志文件示例:
Out - Cloning into''... ERROR - 签出文件:22%(666/2971) 错误 - 签出文件:23%(684/2971) 错误 - 签出文件:24%(714/2971)
您可以通过将git clone命令放在高级函数中来执行此操作,例如:
function Clone-Git {
[CmdletBinding()]
param($repoUrl, $localRepoDir)
git clone $repoUrl $localRepoDir
}
Clone-Git $RepositoryUrl $localRepoDirectory -ev cloneErrors
$cloneErrors
如果你使用System.Diagnostics.Process
启动Git,你可以重定向所有的错误和输出。
我只需要为Inkscape解决这个问题:
$si = New-Object System.Diagnostics.ProcessStartInfo
$si.Arguments = YOUR PROCESS ARGS
$si.UseShellExecute = $false
$si.RedirectStandardOutput = $true
$si.RedirectStandardError = $true
$si.WorkingDirectory = $workingDir
$si.FileName = EXECUTABLE LOCATION
$process = [Diagnostics.Process]::Start($si)
while (!($process.HasExited))
{
// Do what you want with strerr and stdout
Start-Sleep -s 1 // Sleep for 1 second
}
当然,您可以将此包装在具有适当参数的函数中...
以上是关于PowerShell - 将可执行文件的stderr重定向到文件或变量,但仍然有stdout转到控制台的主要内容,如果未能解决你的问题,请参考以下文章
在 RAMDisk 上执行二进制文件是不是会将可执行文件重新加载到内存中?
AWS Lambda:将可执行文件与 python 一起使用
Maven:编译时将可执行文件(.exe)移动到目标文件夹中
为啥 ld 在将可执行文件链接到 a 时需要 -rpath-link 以便需要另一个 so ?