使用 C# 捕获进程输出
在 .net 中捕獲進程輸出
Intro
很多時候我們可能會需要執行一段命令獲取一個輸出,遇到的比較典型的就是之前我們需要用 FFMpeg 實現視頻的編碼壓縮水印等一系列操作,當時使用的是 FFMpegCore 這個類庫,這個類庫的實現原理是啟動另外一個進程,啟動 ffmpeg 并傳遞相應的處理參數,并根據進程輸出獲取處理進度,處理結果等信息
為了方便使用,實現了兩個幫助類來方便的獲取進程的輸出,分別是 ProcessExecutor 和 CommandRunner,前者更為靈活,可以通過事件添加自己的額外事件訂閱處理,后者為簡化版,主要是只獲取輸出的場景,兩者的實現原理大體是一樣的,啟動一個 Process,并監聽其輸出事件獲取輸出
ProcessExecutor
使用示例,這個示例是獲取保存 nuget 包的路徑的一個示例:
using?var?executor?=?new?ProcessExecutor("dotnet",?"nuget?locals?global-packages?-l"); var?folder?=?string.Empty; executor.OnOutputDataReceived?+=?(sender,?str)?=> {if(str?is?null)return;Console.WriteLine(str);if(str.StartsWith("global-packages:")){folder?=?str.Substring("global-packages:".Length).Trim();????????????????????} }; executor.Execute();Console.WriteLine(folder);ProcessExecutor 實現代碼如下:
public?class?ProcessExecutor?:?IDisposable {public?event?EventHandler<int>?OnExited;public?event?EventHandler<string>?OnOutputDataReceived;public?event?EventHandler<string>?OnErrorDataReceived;protected?readonly?Process?_process;protected?bool?_started;public?ProcessExecutor(string?exePath)?:?this(new?ProcessStartInfo(exePath)){}public?ProcessExecutor(string?exePath,?string?arguments)?:?this(new?ProcessStartInfo(exePath,?arguments)){}public?ProcessExecutor(ProcessStartInfo?startInfo){_process?=?new?Process(){StartInfo?=?startInfo,EnableRaisingEvents?=?true,};_process.StartInfo.UseShellExecute?=?false;_process.StartInfo.CreateNoWindow?=?true;_process.StartInfo.RedirectStandardOutput?=?true;_process.StartInfo.RedirectStandardInput?=?true;_process.StartInfo.RedirectStandardError?=?true;}protected?virtual?void?InitializeEvents(){_process.OutputDataReceived?+=?(sender,?args)?=>{if?(args.Data?!=?null){OnOutputDataReceived?.Invoke(sender,?args.Data);}};_process.ErrorDataReceived?+=?(sender,?args)?=>{if?(args.Data?!=?null){OnErrorDataReceived?.Invoke(sender,?args.Data);}};_process.Exited?+=?(sender,?args)?=>{if?(sender?is?Process?process){OnExited?.Invoke(sender,?process.ExitCode);}else{OnExited?.Invoke(sender,?_process.ExitCode);}};}protected?virtual?void?Start(){if?(_started){return;}_started?=?true;_process.Start();_process.BeginOutputReadLine();_process.BeginErrorReadLine();_process.WaitForExit();}public?async?virtual?Task?SendInput(string?input){try{await?_process.StandardInput.WriteAsync(input!);}catch?(Exception?e){OnErrorDataReceived?.Invoke(_process,?e.ToString());}}public?virtual?int?Execute(){InitializeEvents();Start();return?_process.ExitCode;}public?virtual?async?Task<int>?ExecuteAsync(){InitializeEvents();return?await?Task.Run(()?=>{Start();return?_process.ExitCode;}).ConfigureAwait(false);}public?virtual?void?Dispose(){_process.Dispose();OnExited?=?null;OnOutputDataReceived?=?null;OnErrorDataReceived?=?null;} }CommandExecutor
上面的這種方式比較靈活但有些繁瑣,于是有了下面這個版本
使用示例:
[Fact] public?void?HostNameTest() {if?(!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)){return;}var?result?=?CommandRunner.ExecuteAndCapture("hostname");var?hostName?=?Dns.GetHostName();Assert.Equal(hostName,?result.StandardOut.TrimEnd());Assert.Equal(0,?result.ExitCode); }實現源碼:
public?static?class?CommandRunner {public?static?int?Execute(string?commandPath,?string?arguments?=?null,?string?workingDirectory?=?null){using?var?process?=?new?Process(){StartInfo?=?new?ProcessStartInfo(commandPath,?arguments????string.Empty){UseShellExecute?=?false,CreateNoWindow?=?true,WorkingDirectory?=?workingDirectory????Environment.CurrentDirectory}};process.Start();process.WaitForExit();return?process.ExitCode;}public?static?CommandResult?ExecuteAndCapture(string?commandPath,?string?arguments?=?null,?string?workingDirectory?=?null){using?var?process?=?new?Process(){StartInfo?=?new?ProcessStartInfo(commandPath,?arguments????string.Empty){UseShellExecute?=?false,CreateNoWindow?=?true,RedirectStandardOutput?=?true,RedirectStandardError?=?true,WorkingDirectory?=?workingDirectory????Environment.CurrentDirectory}};process.Start();var?standardOut?=?process.StandardOutput.ReadToEnd();var?standardError?=?process.StandardError.ReadToEnd();process.WaitForExit();return?new?CommandResult(process.ExitCode,?standardOut,?standardError);} }public?sealed?class?CommandResult {public?CommandResult(int?exitCode,?string?standardOut,?string?standardError){ExitCode?=?exitCode;StandardOut?=?standardOut;StandardError?=?standardError;}public?string?StandardOut?{?get;?}public?string?StandardError?{?get;?}public?int?ExitCode?{?get;?} }More
如果只要執行命令獲取是否執行成功則使用 CommandRunner.Execute 即可,只獲取輸出和是否成功可以用 CommandRunner.ExecuteAndCapture 方法,如果想要進一步的添加事件訂閱則使用 ProcessExecutor
Reference
https://github.com/rosenbjerg/FFMpegCore
https://github.com/WeihanLi/WeihanLi.Common/blob/dev/src/WeihanLi.Common/Helpers/ProcessExecutor.cs
https://github.com/WeihanLi/WeihanLi.Common/blob/dev/test/WeihanLi.Common.Test/HelpersTest/ProcessExecutorTest.cs
https://github.com/WeihanLi/WeihanLi.Common/blob/dev/test/WeihanLi.Common.Test/HelpersTest/CommandRunnerTest.cs
總結
以上是生活随笔為你收集整理的使用 C# 捕获进程输出的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于.NetCore3.1系列 —— 日
- 下一篇: Blazor带我重玩前端(六)