还在手写 Process?试试 CliWrap 简化 C# 命令行操作

51 阅读3分钟

前言

C#开发中,执行命令行操作是常见需求,但使用原生Process类往往面临代码冗长、异步支持差、错误处理繁琐等问题。

本文将介绍一款命令行交互库——CliWrap,它通过流式API设计和现代化特性,让命令行操作变得简洁、安全且强大。

为什么需要CliWrap?

传统Process类的问题

使用原生Process类时,常遇到以下问题:

  • 代码冗长:需要大量样板代码处理进程启动和输出重定向

  • 死锁风险:不当的流处理容易导致进程挂起

  • 异步支持差:缺乏原生async/await支持

  • 错误处理繁琐:退出码判断和异常处理逻辑分散

  • 管道操作复杂:多进程链式调用实现困难

CliWrap的优势

  • 流式API设计:链式配置,代码直观易读

  • 完全异步:原生支持async/await模式

  • 智能管道系统:轻松实现进程间数据传输

  • 多执行模式:缓冲、事件流、管道等灵活选择

  • 安全性保障:自动参数转义,防止注入攻击

安装与配置

# NuGet包管理器
Install-Package CliWrap

# .NET CLI
dotnet add package CliWrap

兼容性

  • .NET Standard 2.0+

  • .NET Core 3.0+

  • .NET Framework 4.6.2+

  • 跨平台支持(Windows/Linux/macOS)

核心功能

1、基础命令执行

传统方式

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方式

using CliWrap;

var result = await Cli.Wrap("git")
    .WithArguments(["status"])
    .WithWorkingDirectory("D:/myproject/18csharp-code")
    .ExecuteAsync();

Console.WriteLine($"Exit code: {result.ExitCode}");
Console.WriteLine($"Run time: {result.RunTime}");

2、输出缓冲处理

try {
    var result = await Cli.Wrap("cmd")
        .WithArguments(["/c", "dir", "."])
        .ExecuteBufferedAsync(Encoding.UTF8);

    if (result.IsSuccess) {
        Console.WriteLine("Success:");
        Console.WriteLine(result.StandardOutput);
    } else {
        Console.WriteLine($"Failed with code {result.ExitCode}");
        Console.WriteLine(result.StandardError);
    }
} catch (Exception ex) {
    Console.WriteLine($"Exception: {ex.Message}");
}

3、实时事件流处理

await foreach (var cmdEvent in Cli.Wrap("cmd")
    .WithArguments(["/c", "dir", ".", "/w"])
    .ListenAsync()) 
{
    switch (cmdEvent) {
        case StartedCommandEvent started:
            Console.WriteLine($"Process started, PID: {started.ProcessId}");
            break;
        case StandardOutputCommandEvent stdOut:
            Console.WriteLine($"Output: {stdOut.Text}");
            break;
        case ExitedCommandEvent exited:
            Console.WriteLine($"Process exited with code {exited.ExitCode}");
            break;
    }
}

4、管道操作

// 链式执行命令
var result = await (
    Cli.Wrap("cmd").WithArguments(["/c", "echo", "Hello"]) |
    Cli.Wrap("findstr").WithArguments(["Hello"]) |
    PipeTarget.ToFile("output.txt")
).ExecuteAsync();

// 读取结果
if (File.Exists("output.txt")) {
    var content = await File.ReadAllTextAsync("output.txt");
    Console.WriteLine(content.Trim());
}

5、超时与取消控制

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));

try {
    var result = await Cli.Wrap("ping")
        .WithArguments(["8.8.8.8", "-t"])
        .ExecuteAsync(cts.Token);
} catch (OperationCanceledException) {
    Console.WriteLine("Command cancelled");
}

实战模板

通用命令执行器

public class CommandExecutor {
    public async Task<(bool Success, string Output, string Error)> 
        ExecuteAsync(string fileName, string[] args, 
                    string workingDir = null, 
                    TimeSpan? timeout = null) 
    {
        var cmd = Cli.Wrap(fileName).WithArguments(args);
        if (workingDir != null) cmd = cmd.WithWorkingDirectory(workingDir);
        
        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, "", "Timeout expired");
        }
    }
}

实时输出处理器

public class RealTimeProcessor {
    public async Task ProcessWithCallback(string fileName, string[] args,
                                         Action<string> onOutput = null,
                                         Action<string> onError = null) 
    {
        await foreach (var cmdEvent in Cli.Wrap(fileName)
            .WithArguments(args)
            .ListenAsync()) 
        {
            switch (cmdEvent) {
                case StandardOutputCommandEvent stdOut:
                    onOutput?.Invoke(stdOut.Text);
                    break;
                case StandardErrorCommandEvent stdErr:
                    onError?.Invoke(stdErr.Text);
                    break;
            }
        }
    }
}

性能建议

1、内存管理:大量输出时使用事件流模式

2、并发控制:用SemaphoreSlim限制并发进程数

3、资源清理:及时释放CancellationTokenSource

4、错误处理:区分预期错误和系统异常

总结

CliWrap通过以下特性提升C#命令行交互体验:

  • 流式API设计使代码更直观

  • 原生异步支持提升响应能力

  • 完善的管道系统简化复杂操作

  • 生产级特性保障系统稳定性

用上 CliWrap 之后,命令行交互这块基本不用操心了。自动化脚本、CI/CD 流水线、工控软件……很多 .NET 项目现在都直接拿它当标准工具。

关键词

CliWrap、C#、命令行交互、异步编程、管道操作、进程管理、CliWrap、C#、命令行、Process、异步执行、管道操作、事件流、超时控制、.NET、系统集成

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!

来源:mp.weixin.qq.com/s/tBf2l3oZTXg4zkr__MHpJQ