PowerShell调用Process运行curl挂起及无法获取错误输出问题
问题根因
两种写法的故障原因都非常明确:
- 原生
System.Diagnostics.Process写法永久挂起是典型的流缓冲区死锁:你同时重定向了标准输出、标准错误流,但在进程完全退出前没有读取流内容。Windows给子进程分配的单流缓冲区大小通常只有4KB,当curl输出的内容(含正常进度、报错信息)填满缓冲区后,curl会被阻塞在流写入操作上无法退出,而你的代码正卡在WaitForExit()等待curl结束,两边互相等待就形成死锁。输出内容少的时候缓冲区没满,就会偶发运行正常。 Start-Process写法拿不到错误输出,是因为你调用时没有指定流重定向参数:默认配置下Start-Process不会捕获子进程的标准输出、标准错误流,进程对象的StandardError、StandardOutput属性自然始终为空。
解决方案
两种稳定方案都可以同时避免死锁、正常获取错误输出,按需选择即可。
方案1:修正原生Process类调用(无临时文件开销)
核心改动是在进程启动后立刻异步读取两个流的内容,从根源避免缓冲区写满阻塞:
$curlPath = "curl.exe" $curlArgs = "sftp://download.url.com/filename_20220618.zip --user username:psw -o D:\filename_20220618.zip -k" $pinfo = New-Object System.Diagnostics.ProcessStartInfo $pinfo.FileName = $curlPath $pinfo.Arguments = $curlArgs $pinfo.RedirectStandardError = $true $pinfo.RedirectStandardOutput = $true $pinfo.UseShellExecute = $false $pinfo.CreateNoWindow = $true $curlProcess = New-Object System.Diagnostics.Process $curlProcess.StartInfo = $pinfo # 初始化存储流内容的容器 $stdoutBuilder = New-Object System.Text.StringBuilder $stderrBuilder = New-Object System.Text.StringBuilder # 注册流接收回调 $curlProcess.add_OutputDataReceived({ param($sender, $eventArgs) if ($null -ne $eventArgs.Data) { [void]$stdoutBuilder.AppendLine($eventArgs.Data) } }) $curlProcess.add_ErrorDataReceived({ param($sender, $eventArgs) if ($null -ne $eventArgs.Data) { [void]$stderrBuilder.AppendLine($eventArgs.Data) } }) $curlProcess.Start() | Out-Null # 启动异步流读取 $curlProcess.BeginOutputReadLine() $curlProcess.BeginErrorReadLine() $curlProcess.WaitForExit() # 二次WaitForExit确保异步流读取完成 $curlProcess.WaitForExit() # 提取结果 $exitCode = $curlProcess.ExitCode $stdoutContent = $stdoutBuilder.ToString().Trim() $stderrContent = $stderrBuilder.ToString().Trim() # 业务逻辑示例:执行失败时打印错误 if ($exitCode -ne 0) { Write-Error "curl下载失败,退出码[$exitCode],错误信息:$stderrContent" }
方案2:正确使用Start-Process(写法更简洁)
通过Start-Process自带的流重定向参数,将输出写入临时文件后读取,PowerShell内部会自动处理流读取逻辑,不会出现死锁:
$curlPath = "curl.exe" $curlArgs = "sftp://download.url.com/filename_20220618.zip --user username:psw -o D:\filename_20220618.zip -k" # 创建临时文件存储流内容 $stdoutTemp = [System.IO.Path]::GetTempFileName() $stderrTemp = [System.IO.Path]::GetTempFileName() try { $curlProcess = Start-Process -FilePath $curlPath -ArgumentList $curlArgs -Wait -PassThru ` -WindowStyle Hidden -NoNewWindow ` -RedirectStandardOutput $stdoutTemp -RedirectStandardError $stderrTemp $exitCode = $curlProcess.ExitCode $stdoutContent = Get-Content -Path $stdoutTemp -Raw -Encoding UTF8 | ForEach-Object { $_.Trim() } $stderrContent = Get-Content -Path $stderrTemp -Raw -Encoding UTF8 | ForEach-Object { $_.Trim() } if ($exitCode -ne 0) { Write-Error "curl下载失败,退出码[$exitCode],错误信息:$stderrContent" } } finally { # 执行完成后清理临时文件 Remove-Item -Path $stdoutTemp, $stderrTemp -ErrorAction SilentlyContinue }
注意事项
不要使用同步顺序读取流的写法(例如先调用$curlProcess.StandardOutput.ReadToEnd()再读取StandardError),如果错误流先填满缓冲区,这种写法依然会触发死锁。
内容的提问来源于stack exchange,提问作者DBZHOU
相关产品推荐
相关产品推荐

