CliWrap是一个用于与外部命令行界面交互的库。它提供了一个便捷的模型,用于启动进程、重定向输入和输出流、等待完成、处理取消等。
通过使用本项目或其源代码,无论出于何种目的和以何种形式,您默认同意以下所有声明:
要了解更多关于战争的信息以及如何提供帮助,请点击这里。荣耀属于乌克兰!🇺🇦
dotnet add package CliWrap
System.Diagnostics.Process
的严密抽象您可以观看以下视频之一来了解如何使用该库:
与shell类似,CliWrap的基本工作单元是命令—一个封装了运行进程指令的对象。要构建一个命令,首先调用Cli.Wrap(...)
并传入可执行文件路径,然后使用提供的流畅接口来配置参数、工作目录或其他选项。一旦命令配置完成,您可以通过调用ExecuteAsync()
来运行它:
using CliWrap; var result = await Cli.Wrap("path/to/exe") .WithArguments(["--foo", "bar"]) .WithWorkingDirectory("work/dir/path") .ExecuteAsync(); // 结果包含: // -- result.IsSuccess (bool) // -- result.ExitCode (int) // -- result.StartTime (DateTimeOffset) // -- result.ExitTime (DateTimeOffset) // -- result.RunTime (TimeSpan)
上面的代码使用配置的命令行参数和工作目录启动一个子进程,然后异步等待它退出。任务完成后,它会解析为一个CommandResult
对象,其中包含进程退出代码和其他相关信息。
警告: 如果底层进程返回非零退出代码,CliWrap将抛出异常,因为这通常表示发生了错误。 您可以通过使用
WithValidation(CommandResultValidation.None)
禁用结果验证来覆盖此行为。
默认情况下,进程的标准输入、输出和错误流被路由到CliWrap等效的空设备,它表示一个空源和一个丢弃所有数据的目标。
您可以通过调用WithStandardInputPipe(...)
、WithStandardOutputPipe(...)
或WithStandardErrorPipe(...)
来为相应的流配置管道,从而更改此行为:
using CliWrap; var stdOutBuffer = new StringBuilder(); var stdErrBuffer = new StringBuilder(); var result = await Cli.Wrap("path/to/exe") .WithArguments(["--foo", "bar"]) .WithWorkingDirectory("work/dir/path") // 这可以通过`ExecuteBufferedAsync()`简化 .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) .ExecuteAsync(); // 以字符串形式访问内存中缓冲的stdout和stderr var stdOut = stdOutBuffer.ToString(); var stdErr = stdErrBuffer.ToString();
这个示例命令被配置为将写入标准输出和错误流的数据解码为文本,并将其附加到相应的StringBuilder
缓冲区。执行完成后,可以检查这些缓冲区以查看进程在控制台上打印的内容。
处理命令输出是一个非常常见的用例,因此CliWrap提供了一些高级执行模型来简化这些场景。特别是,上面显示的相同内容也可以使用ExecuteBufferedAsync()
扩展方法更简洁地实现:
using CliWrap; using CliWrap.Buffered; // 调用`ExecuteBufferedAsync()`而不是`ExecuteAsync()` // 隐式配置将写入内存缓冲区的管道。 var result = await Cli.Wrap("path/to/exe") .WithArguments(["--foo", "bar"]) .WithWorkingDirectory("work/dir/path") .ExecuteBufferedAsync(); // 结果包含: // -- result.IsSuccess (bool) // -- result.StandardOutput (string) // -- result.StandardError (string) // -- result.ExitCode (int) // -- result.StartTime (DateTimeOffset) // -- result.ExitTime (DateTimeOffset) // -- result.RunTime (TimeSpan)
警告: 使用
ExecuteBufferedAsync()
时要谨慎。 程序可以向输出和错误流写入任意数据(包括二进制数据),将其存储在内存中可能不切实际。 对于更高级的场景,CliWrap还提供了其他管道选项,这些在管道部分中有介绍。
命令对象提供的流畅接口允许您配置其执行的各个方面。本节涵盖了所有可用的配置方法及其用法。
注意:
Command
是一个不可变对象 — 这里列出的所有配置方法都会创建一个新实例,而不是修改现有实例。
WithArguments(...)
设置传递给子进程的命令行参数。
默认值:空。
示例:
var cmd = Cli.Wrap("git") // 每个元素被格式化为单独的参数。 // 等效于:`git commit -m "my commit"` .WithArguments(["commit", "-m", "my commit"]);
var cmd = Cli.Wrap("git") // 每个 Add(...) 调用会自动处理格式化。 // 等同于: `git clone https://github.com/Tyrrrz/CliWrap --depth 20` .WithArguments(args => args .Add("clone") .Add("https://github.com/Tyrrrz/CliWrap") .Add("--depth") .Add(20) );
var forcePush = true; var cmd = Cli.Wrap("git") // 参数也可以以命令式方式构造。 // 等同于: `git push --force` .WithArguments(args => { args.Add("push"); if (forcePush) args.Add("--force"); });
注意: 构建器重载允许您定义自定义扩展方法以实现可重用的参数模式。 了解更多。
var cmd = Cli.Wrap("git") // 除非必要,否则避免使用此重载。 // 等同于: `git commit -m "my commit"` .WithArguments("commit -m \"my commit\"");
警告: 除非绝对必要,否则避免直接从字符串设置命令行参数。 此方法要求所有参数都提前正确转义和格式化 — 自己做这件事可能会很麻烦。 格式化错误可能导致意外的错误和安全漏洞。
WithWorkingDirectory(...)
设置子进程的工作目录。
默认值: 当前工作目录,即 Directory.GetCurrentDirectory()
。
示例:
var cmd = Cli.Wrap("git") .WithWorkingDirectory("c:/projects/my project/");
WithEnvironmentVariables(...)
设置暴露给子进程的额外环境变量。
默认值: 空。
示例:
var cmd = Cli.Wrap("git") .WithEnvironmentVariables(env => env .Set("GIT_AUTHOR_NAME", "John") .Set("GIT_AUTHOR_EMAIL", "john@email.com") );
var cmd = Cli.Wrap("git") .WithEnvironmentVariables(new Dictionary<string, string?> { ["GIT_AUTHOR_NAME"] = "John", ["GIT_AUTHOR_EMAIL"] = "john@email.com" });
注意: 使用
WithEnvironmentVariables(...)
配置的环境变量是在从父进程继承的环境变量之上应用的。 如果需要删除继承的变量,请将相应的值设置为null
。
WithCredentials(...)
设置应该以其身份启动子进程的用户的域、名称和密码。
默认值: 无凭据。
示例:
var cmd = Cli.Wrap("git") .WithCredentials(creds => creds .SetDomain("some_workspace") .SetUserName("johndoe") .SetPassword("securepassword123") .LoadUserProfile() );
var cmd = Cli.Wrap("git") .WithCredentials(new Credentials( domain: "some_workspace", userName: "johndoe", password: "securepassword123", loadUserProfile: true ));
警告: 在不同用户名下运行进程在所有平台上都得到支持,但其他选项仅在 Windows 上可用。
WithValidation(...)
设置验证执行结果的策略。
接受的值:
CommandResultValidation.None
— 不验证CommandResultValidation.ZeroExitCode
— 确保进程退出时退出代码为零默认值: CommandResultValidation.ZeroExitCode
。
示例:
var cmd = Cli.Wrap("git") .WithValidation(CommandResultValidation.ZeroExitCode);
var cmd = Cli.Wrap("git") .WithValidation(CommandResultValidation.None);
如果您想在进程以非零退出代码退出时抛出自定义异常,请不要禁用结果验证,而是捕获默认的 CommandExecutionException
并在您自己的异常中重新抛出它。
这样您可以保留原始异常提供的信息,同时用额外的上下文扩展它:
try { await Cli.Wrap("git").ExecuteAsync(); } catch (CommandExecutionException ex) { // 重新抛出原始异常以保留有关失败命令的附加信息 // (退出代码、参数等)。 throw new MyException("无法运行 git 命令行工具。", ex); }
WithStandardInputPipe(...)
设置将用于进程标准输入流的管道源。
默认值: PipeSource.Null
。
在管道部分中阅读有关此方法的更多信息。
WithStandardOutputPipe(...)
设置将用于进程标准输出流的管道目标。
默认值: PipeTarget.Null
。
在管道部分中阅读有关此方法的更多信息。
WithStandardErrorPipe(...)
设置将用于进程标准错误流的管道目标。
默认值: PipeTarget.Null
。
在管道部分中阅读有关此方法的更多信息。
CliWrap 提供了一个非常强大和灵活的管道模型,允许您重定向进程的流、转换输入和输出数据,甚至以最小的努力将多个命令链接在一起。
它的核心基于两个抽象:为标准输入流提供数据的 PipeSource
和读取来自标准输出流或标准错误流的数据的 PipeTarget
。
默认情况下,命令的输入管道设置为 PipeSource.Null
,输出和错误管道设置为 PipeTarget.Null
。
这些对象有效地代表无操作存根,分别提供空输入和丢弃所有输出。
您可以通过在命令上调用相应的配置方法来指定自己的 PipeSource
和 PipeTarget
实例:
await using var input = File.OpenRead("input.txt"); await using var output = File.Create("output.txt"); await Cli.Wrap("foo") .WithStandardInputPipe(PipeSource.FromStream(input)) .WithStandardOutputPipe(PipeTarget.ToStream(output)) .ExecuteAsync();
或者,也可以使用管道运算符以稍微简洁的方式配置管道:
await using var input = File.OpenRead("input.txt"); await using var output = File.Create("output.txt"); await (input | Cli.Wrap("foo") | output).ExecuteAsync();
PipeSource
和 PipeTarget
都有许多工厂方法,可以让您为不同的场景创建管道实现:
PipeSource
:
PipeSource.Null
— 代表一个空的管道源PipeSource.FromStream(...)
— 从任何可读流中管道数据PipeSource.FromFile(...)
— 从文件中管道数据PipeSource.FromBytes(...)
— 从字节数组中管道数据PipeSource.FromString(...)
— 从文本字符串中管道数据PipeSource.FromCommand(...)
— 从另一个命令的标准输出中管道数据PipeTarget
:
PipeTarget.Null
— 代表丢弃所有数据的管道目标PipeTarget.ToStream(...)
— 将数据管道到任何可写流PipeTarget.ToFile(...)
— 将数据管道到文件PipeTarget.ToStringBuilder(...)
— 将数据作为文本管道到 StringBuilder
PipeTarget.ToDelegate(...)
— 将数据作为文本,逐行管道到 Action<string>
、Func<string, Task>
或 Func<string, CancellationToken, Task>
委托PipeTarget.Merge(...)
— 通过将相同的数据复制到所有管道来合并多个出站管道警告: 使用
PipeTarget.Null
会导致底层进程根本不打开相应的流(stdout 或 stderr)。 在绝大多数情况下,这种行为在功能上应该等同于管道到空流,但没有消耗和丢弃不需要的数据的性能开销。 在某些情况下这可能是不希望的 — 在这种情况下,建议使用PipeTarget.ToStream(Stream.Null)
显式地管道到空流。
以下是一些使用 CliWrap 的管道功能可以实现的示例:
var cmd = "Hello world" | Cli.Wrap("foo"); await cmd.ExecuteAsync();
StringBuilder
:var stdOutBuffer = new StringBuilder(); var cmd = Cli.Wrap("foo") | stdOutBuffer; await cmd.ExecuteAsync();
using var httpClient = new HttpClient(); await using var input = await httpClient.GetStreamAsync("https://example.com/image.png"); var cmd = input | Cli.Wrap("foo"); await cmd.ExecuteAsync();
var cmd = Cli.Wrap("foo") | Cli.Wrap("bar") | Cli.Wrap("baz"); await cmd.ExecuteAsync();
await using var stdOut = Console.OpenStandardOutput(); await using var stdErr = Console.OpenStandardError(); var cmd = Cli.Wrap("foo") | (stdOut, stdErr); await cmd.ExecuteAsync();
var cmd = Cli.Wrap("foo") | Debug.WriteLine; await cmd.ExecuteAsync();
StringBuilder
:var buffer = new StringBuilder(); var cmd = Cli.Wrap("foo") | (PipeTarget.ToFile("output.txt"), PipeTarget.ToStringBuilder(buffer)); await cmd.ExecuteAsync();
var target = PipeTarget.Merge( PipeTarget.ToFile("file1.txt"), PipeTarget.ToFile("file2.txt"), PipeTarget.ToFile("file3.txt") ); var cmd = Cli.Wrap("foo") | target; await cmd.ExecuteAsync();
var cmd = "Hello world" | Cli.Wrap("foo").WithArguments(["aaa"]) | Cli.Wrap("bar").WithArguments(["bbb"]) | (Console.WriteLine, Console.Error.WriteLine); await cmd.ExecuteAsync();
CliWrap提供了几种高级执行模型,为命令提供了替代的思考方式。 这些本质上只是利用前面展示的管道功能的扩展方法。
这种执行模型允许你运行一个进程,同时将其标准输出和错误流作为文本缓冲在内存中。 缓冲的数据可以在命令执行完成后访问。
要使用缓冲执行命令,调用ExecuteBufferedAsync()
扩展方法:
using CliWrap; using CliWrap.Buffered; var result = await Cli.Wrap("foo") .WithArguments(["bar"]) .ExecuteBufferedAsync(); var exitCode = result.ExitCode; var stdOut = result.StandardOutput; var stdErr = result.StandardError;
默认情况下,ExecuteBufferedAsync()
假设底层进程使用默认编码(Console.OutputEncoding
)将文本写入控制台。
要覆盖此设置,请使用可用的重载方法之一明确指定编码:
// 将stdout和stderr都视为UTF8编码的文本流 var result = await Cli.Wrap("foo") .WithArguments(["bar"]) .ExecuteBufferedAsync(Encoding.UTF8); // 将stdout视为ASCII编码,stderr视为UTF8编码 var result = await Cli.Wrap("foo") .WithArguments(["bar"]) .ExecuteBufferedAsync(Encoding.ASCII, Encoding.UTF8);
注意: 如果底层进程返回非零退出代码,
ExecuteBufferedAsync()
将抛出异常,类似于ExecuteAsync()
,但异常消息还将包含标准错误数据。
除了将命令作为任务执行外,CliWrap