在 .net 中捕获进程输出

Intro

很多时候我们可能会需要执行一段命令获取一个输出,遇到的比较典型的就是之前我们需要用 FFMpeg 实现视频的编码压缩水印等一系列操作,当时使用的是 FFMpegCore 这个类库,这个类库的实现原理是启动另外一个进程,启动 ffmpeg 并传递相应的处理参数,并根据进程输出获取处理进度,处理结果等信息

为了方便使用,实现了两个帮助类来方便的获取进程的输出,分别是 ProcessExecutorCommandRunner,前者更为灵活,可以通过事件添加自己的额外事件订阅处理,后者为简化版,主要是只获取输出的场景,两者的实现原理大体是一样的,启动一个 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# 捕获进程输出相关推荐

  1. linux重定向进程输出到文件删除,Linux文件编辑器vim输出输入重定向、管道以及进程(示例代码)...

    合抱之木,生于毫末:九层之台,起于累土:千里之行,始于足下.正因基础的重要性,才又撸起了linux. linux编辑工具: VI VIM EMACS vim 是vi的升级版本,它不仅兼容vi的所有指令 ...

  2. 驱动列举进程输出到应用层

    本篇算是前两篇的综合,驱动列举出进程,并将进程名加入到一个链表中, 最后应用层程序通过IOCTL读出驱动传递出来的数据. 驱动irp3.h文件: #include <ntddk.h>   ...

  3. java执行exe 没捕获到输出_Java程序员注意——六种异常处理的陋习

    你觉得自己是一个Java专家吗?是否肯定自己已经全面掌握了Java的异常处理机制?在下面这段代码中,你能够迅速找出异常处理的六个问题吗? OutputStreamWriter out = ... ja ...

  4. 使用windows钩子捕获进程的启动和关闭消息

    2012年12月13日补充: 这篇文章写的时候是我还在上学的时候,所以不管是从技术实现角度还是文笔都显得很嫩,在此向所有无意间看到这篇文章的人表示抱歉.我写了这篇文章之后2年有人想问我要源代码,唉,如 ...

  5. [Python]关于使用subprocess.run捕获shell输出的方法

    subprocess模块文档https://docs.python.org/zh-cn/3/library/subprocess.html 方法 p=subprocess.run([args],std ...

  6. python stdout_如何从Python函数调用中捕获stdout输出?

    试试这个上下文管理器:from cStringIO import StringIO import sys class Capturing(list): def __enter__(self): sel ...

  7. linux查看运行的程序c pu,在Linux系统中,采用()一命令查看进程输出的信息,得到下图所示的结果。系统启动时最先运行的进程是...

    Routing.protocols use different techniques for assigning[S1]to individual networks.Further,each rout ...

  8. 一条命令,根据进程名判断有进程输出up,无进程无输出

    这个研究了好一会, 由于开发需要,提供的命令. shell命令,可以按照分号分割,也可以按照换行符分割.如果想一行写入多个命令,可以通过"';"分割. a=`ps -ef | gr ...

  9. python程序运行结果不停_关于python:在进程运行时不断打印Subprocess输出

    要从我的python脚本启动程序,我使用以下方法: def execute(command): process = subprocess.Popen(command, shell=True, stdo ...

最新文章

  1. 6.神操作(把master上的三个安装包scp给slave)—Hadoop完全分布式搭建完成
  2. 在windows下运行Felzenszwalb的Deformable Part Model(DPM)源码voc-release3.1来训练自己的模型
  3. dump解析入门-用VS解析dump文件进行排障
  4. 谈操作系统的碎片化和融合
  5. 2.6、ConfigurationClassPostProcessor 解析配置文件
  6. jquery操作滚动条滚动到指定位置
  7. 数据结构 实验五(银行叫号系统)
  8. 企业全链路数字化营销一站式解决方案
  9. 拼途网: 从线上到线下的拼途旅行社区
  10. 面经(5) 2020/4/5 Java研发实习生 蚂蚁金服
  11. 虚拟盘客户机文件盒服务器不一样,VMware ESX三种虚拟磁盘类型分析
  12. dubbo之@Adaptive注解分析
  13. 蓝桥杯 STEMA 考试选择题模拟题
  14. 截流式合流制设计流量计算_截流式合流制管道系统的设计说明
  15. Openstack 组件Placement部署思路过程
  16. 扩展Euclidean算法求乘法逆原理详解与算法实现
  17. #includelt;和#include
  18. Java中线程池详解
  19. 用html和js编写人机象棋对战
  20. 智合同丨合同智能审查、合同要素提取开放API试用

热门文章

  1. Unity中Instantiate一个prefab时需要注意的问题
  2. [leetcode]347. Top K Frequent Elements
  3. Eclipse将引用了第三方jar包的Java项目打包成jar文件
  4. 设置Windows 10时如何创建本地帐户
  5. ubuntu自定义菜单_如何自定义Ubuntu的每日消息
  6. Array 的一些常用 API
  7. JAVA代码—算法基础:数独问题(Sodoku Puzzles)
  8. RIL接听电话没有声音的问题 [ RIL_Answer | RIL_SetAudioDevices ]
  9. 【线性筛】【质因数分解】【约数个数定理】hdu6069 Counting Divisors
  10. Facebook为Messenger应用添加群组付款功能