还在用繁琐的Process类执行命令行操作吗?还在为进程输出重定向、错误处理、异步执行而头疼吗?今天为大家介绍一个游戏规则改变者:CliWrap,一个专为C#开发者打造的命令行交互库,让你的代码更简洁、更安全、更强大!
使用原生Process类执行命令行操作时,开发者经常遇到以下痛点:
Bash# NuGet包管理器
Install-Package CliWrap
# .NET CLI
dotnet add package CliWrap
兼容性:
传统Process方式(代码冗长):
C#var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "git",
Arguments = "status",
UseShellExecute = false,
RedirectStandardOutput = true
}
};
process.Start();
var output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
CliWrap方式(简洁优雅):
C#using System.Diagnostics;
using CliWrap;
namespace AppCliWrap
{
internal class Program
{
static async Task Main(string[] args)
{
var result = await Cli.Wrap("git")
.WithArguments(["status"])
.WithWorkingDirectory("D:\\myproject\\18csharp-code")
.ExecuteAsync();
// 自动包含进程信息
Console.WriteLine($"退出码: {result.ExitCode}");
Console.WriteLine($"执行时间: {result.RunTime}");
}
}
}

应用场景:Git操作、文件处理、系统命令执行
坑点提醒:默认情况下,非零退出码会抛出异常,可通过WithValidation(CommandResultValidation.None)禁用
C#using System.Diagnostics;
using System.Text;
using CliWrap;
using CliWrap.Buffered;
namespace AppCliWrap
{
internal class Program
{
static async Task Main(string[] args)
{
try
{
// 在 Windows 上使用 cmd.exe 来执行 dir 命令
var result = await Cli.Wrap("cmd")
.WithArguments(["/c", "dir", "."]) // /c 表示执行命令后关闭,"." 表示当前目录
.ExecuteBufferedAsync(Encoding.UTF8);
string buildOutput = result.StandardOutput;
string errorOutput = result.StandardError;
if (result.IsSuccess)
{
Console.WriteLine("执行成功:");
Console.WriteLine(buildOutput);
}
else
{
Console.WriteLine($"执行失败,退出代码:{result.ExitCode}");
Console.WriteLine($"错误信息:{errorOutput}");
}
}
catch (Exception ex)
{
Console.WriteLine($"发生异常:{ex.Message}");
}
}
}
}

实际应用场景:编译项目、代码生成、日志分析
最佳实践:对于大量输出的命令,考虑使用事件流模式避免内存溢出
C#using CliWrap;
using CliWrap.EventStream;
using System.Text;
namespace AppCliWrap
{
internal class Program
{
static async Task Main(string[] args)
{
Console.OutputEncoding = Encoding.UTF8;
// 监控当前目录的文件列表
var cmd = Cli.Wrap("cmd")
.WithArguments(["/c", "dir", ".", "/w"]) // /w 参数使输出更紧凑
.WithWorkingDirectory(Environment.CurrentDirectory);
Console.WriteLine("🔍 开始监控目录内容...\n");
await foreach (var cmdEvent in cmd.ListenAsync())
{
switch (cmdEvent)
{
case StartedCommandEvent started:
Console.WriteLine($"🚀 CMD进程启动,PID: {started.ProcessId}");
Console.WriteLine($"📁 监控目录: {Environment.CurrentDirectory}");
Console.WriteLine(new string('-', 60));
break;
case StandardOutputCommandEvent stdOut:
Console.WriteLine($"📤 输出: {stdOut.Text}");
break;
case StandardErrorCommandEvent stdErr:
Console.WriteLine($"❌ 错误: {stdErr.Text}");
break;
case ExitedCommandEvent exited:
Console.WriteLine(new string('-', 60));
Console.WriteLine($"✅ CMD进程退出,代码: {exited.ExitCode}");
break;
}
}
}
}
}

实际应用场景:长时间运行的服务监控、实时日志展示、进度跟踪
性能优势:背压控制机制,防止内存爆炸
C#using CliWrap;
using System.Text;
namespace AppCliWrap
{
internal class Program
{
static async Task Main(string[] args)
{
Console.OutputEncoding = Encoding.UTF8;
Console.WriteLine("=== 命令链式执行 ===\n");
// Windows CMD 版本:echo + findstr
var result1 = await (
Cli.Wrap("cmd").WithArguments(["/c", "echo", "Hello World from Windows"]) |
Cli.Wrap("findstr").WithArguments(["Windows"]) |
PipeTarget.ToFile("output.txt")
).ExecuteAsync();
Console.WriteLine($"✅ 管道执行完成,退出代码: {result1.ExitCode}");
// 读取并显示输出文件内容
if (File.Exists("output.txt"))
{
var content = await File.ReadAllTextAsync("output.txt");
Console.WriteLine($"📄 输出文件内容: {content.Trim()}");
}
Console.WriteLine();
// 另一个例子:获取目录信息并筛选
Console.WriteLine("--- 目录信息筛选 ---");
var result2 = await (
Cli.Wrap("cmd").WithArguments(["/c", "dir", "C:\\Windows\\System32", "/b"]) |
Cli.Wrap("findstr").WithArguments([".exe"]) |
Cli.Wrap("findstr").WithArguments(["/v", "temp"]) | // /v 表示反向匹配(排除)
PipeTarget.ToFile("filtered_files.txt")
).ExecuteAsync();
Console.WriteLine($"✅ 文件筛选完成,退出代码: {result2.ExitCode}");
if (File.Exists("filtered_files.txt"))
{
var lines = await File.ReadAllLinesAsync("filtered_files.txt");
Console.WriteLine($"📊 找到 {lines.Length} 个符合条件的文件");
Console.WriteLine("前5个文件:");
foreach (var line in lines.Take(5))
{
Console.WriteLine($" 📁 {line}");
}
}
}
}
}

实际应用场景:数据处理管线、日志分发、文件批处理
性能提示:管道操作是流式的,不会将整个数据加载到内存
C#using CliWrap;
using System.Text;
namespace AppCliWrap
{
internal class Program
{
static async Task Main(string[] args)
{
Console.OutputEncoding = Encoding.UTF8;
Console.WriteLine("=== 优雅取消机制演示 ===\n");
await ExecuteWithGracefulCancellation();
}
public static async Task ExecuteWithGracefulCancellation()
{
using var forcefulCts = new CancellationTokenSource();
using var gracefulCts = new CancellationTokenSource();
// 15秒后强制终止(保底机制)
forcefulCts.CancelAfter(TimeSpan.FromSeconds(15));
// 10秒后发送中断信号(优雅终止)
gracefulCts.CancelAfter(TimeSpan.FromSeconds(10));
Console.WriteLine("🌐 开始持续 ping 测试...");
Console.WriteLine("⏱️ 10秒后优雅停止,15秒后强制终止");
Console.WriteLine("🔑 按 Ctrl+C 可手动停止");
Console.WriteLine(new string('-', 60));
// 添加手动取消支持
Console.CancelKeyPress += (_, e) => {
e.Cancel = true;
Console.WriteLine("\n🛑 收到中断信号,正在优雅停止...");
gracefulCts.Cancel();
};
try
{
var result = await Cli.Wrap("cmd")
.WithArguments(["/c", "ping", "8.8.8.8", "-t"]) // 持续ping Google DNS
.WithValidation(CommandResultValidation.None)
.ExecuteAsync(forcefulCts.Token, gracefulCts.Token);
Console.WriteLine($"✅ 命令正常完成,退出代码: {result.ExitCode}");
}
catch (OperationCanceledException)
{
Console.WriteLine("⏹️ 命令被取消");
if (gracefulCts.Token.IsCancellationRequested && !forcefulCts.Token.IsCancellationRequested)
{
Console.WriteLine("💫 优雅取消成功");
}
else if (forcefulCts.Token.IsCancellationRequested)
{
Console.WriteLine("⚡ 强制取消执行");
}
}
catch (Exception ex)
{
Console.WriteLine($"❌ 执行出错: {ex.Message}");
}
}
}
}

实际应用场景:网络请求、大文件处理、用户可取消的操作
关键要点:优雅取消相当于发送Ctrl+C信号,进程可自行处理清理工作
C#public class CommandExecutor
{
public async Task<(bool Success, string Output, string Error)>
ExecuteAsync(string fileName, string[] arguments,
string workingDirectory = null,
TimeSpan? timeout = null)
{
var cmd = Cli.Wrap(fileName)
.WithArguments(arguments)
.WithValidation(CommandResultValidation.None);
if (workingDirectory != null)
cmd = cmd.WithWorkingDirectory(workingDirectory);
using var cts = new CancellationTokenSource(timeout ?? TimeSpan.FromMinutes(5));
try
{
var result = await cmd.ExecuteBufferedAsync(cts.Token);
return (result.ExitCode == 0, result.StandardOutput, result.StandardError);
}
catch (OperationCanceledException)
{
return (false, "", "命令执行超时");
}
}
}
C#using CliWrap;
using CliWrap.EventStream;
using System.Text;
namespace AppCliWrap
{
public class RealTimeCommandProcessor
{
public async Task ProcessWithCallback(string fileName, string[] arguments,
Action<string> onOutput = null,
Action<string> onError = null)
{
var cmd = Cli.Wrap(fileName).WithArguments(arguments);
await foreach (var cmdEvent in cmd.ListenAsync())
{
switch (cmdEvent)
{
case StandardOutputCommandEvent stdOut:
onOutput?.Invoke(stdOut.Text);
break;
case StandardErrorCommandEvent stdErr:
onError?.Invoke(stdErr.Text);
break;
}
}
}
}
internal class Program
{
static async Task Main(string[] args)
{
Console.OutputEncoding = Encoding.UTF8;
var processor = new RealTimeCommandProcessor();
Console.WriteLine("🚀 实时命令处理器演示\n");
// 示例1: 基本用法 - Ping 命令
Console.WriteLine("📡 示例1: 实时 Ping 监控");
Console.WriteLine(new string('-', 40));
await processor.ProcessWithCallback(
"ping",
["127.0.0.1", "-n", "5"],
onOutput: output => Console.WriteLine($"✅ {output.Trim()}"),
onError: error => Console.WriteLine($"❌ {error.Trim()}")
);
Console.WriteLine();
// 示例2: 目录列表
Console.WriteLine("📁 示例2: 实时目录列表");
Console.WriteLine(new string('-', 40));
await processor.ProcessWithCallback(
"cmd",
["/c", "dir", "C:\\Windows\\System32", "*.exe"],
onOutput: output => {
if (!string.IsNullOrWhiteSpace(output))
Console.WriteLine($"📄 {output.Trim()}");
},
onError: error => Console.WriteLine($"⚠️ {error.Trim()}")
);
Console.WriteLine();
// 示例3: 系统信息获取
Console.WriteLine("💻 示例3: 系统信息");
Console.WriteLine(new string('-', 40));
await processor.ProcessWithCallback(
"cmd",
["/c", "systeminfo"],
onOutput: output => {
var line = output.Trim();
if (line.Contains("系统类型") || line.Contains("总物理内存") || line.Contains("系统启动时间"))
{
Console.WriteLine($"🔍 {line}");
}
},
onError: error => Console.WriteLine($"❌ 错误: {error.Trim()}")
);
Console.WriteLine("\n🎉 演示完成!");
}
}
}

SemaphoreSlim限制并发进程数量你在项目中是如何处理命令行调用的? 遇到过哪些棘手的问题?欢迎在评论区分享你的经验!
如果这篇文章对你有帮助,请转发给更多需要的同行!让我们一起告别繁琐的Process类,拥抱更优雅的CliWrap!
关注我,获取更多C#开发实战技巧!
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!