Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/Cysharp/ProcessX
Simplify call an external process with the async streams in C# 8.0.
https://github.com/Cysharp/ProcessX
Last synced: about 5 hours ago
JSON representation
Simplify call an external process with the async streams in C# 8.0.
- Host: GitHub
- URL: https://github.com/Cysharp/ProcessX
- Owner: Cysharp
- License: mit
- Created: 2020-01-29T13:57:00.000Z (almost 5 years ago)
- Default Branch: master
- Last Pushed: 2024-09-18T08:27:41.000Z (about 2 months ago)
- Last Synced: 2024-11-05T13:13:03.616Z (6 days ago)
- Language: C#
- Homepage:
- Size: 85.9 KB
- Stars: 455
- Watchers: 20
- Forks: 27
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[![GitHub Actions](https://github.com/Cysharp/ProcessX/workflows/Build-Debug/badge.svg)](https://github.com/Cysharp/ProcessX/actions)
ProcessX
===ProcessX simplifies call an external process with the aync streams in C# 8.0 without complex `Process` code. You can receive standard output results by `await foreach`, it is completely asynchronous and realtime.
![image](https://user-images.githubusercontent.com/46207/73369038-504f0c80-42f5-11ea-8b36-5c5c979ac882.png)
Also provides zx mode to write shell script in C#, details see [Zx](#zx) section.
![image](https://user-images.githubusercontent.com/46207/130373766-0f16e9ad-57ba-446b-81ee-c255c7149035.png)
## Table of Contents
- [Getting Started](#getting-started)
- [Cancellation](#cancellation)
- [Raw Process/StdError Stream](#raw-processstderror-stream)
- [Read Binary Data](#read-binary-data)
- [Change acceptable exit codes](#change-acceptable-exit-codes)
- [Zx](#zx)
- [Reference](#reference)
- [Competitor](#competitor)
- [License](#license)Getting Started
---
Install library from NuGet that support from `.NET Standard 2.0`.> PM> Install-Package [ProcessX](https://www.nuget.org/packages/ProcessX)
Main API is only `Cysharp.Diagnostics.ProcessX.StartAsync` and throws `ProcessErrorException` when error detected.
* **Simple**, only write single string command like the shell script.
* **Asynchronous**, by C# 8.0 async streams.
* **Manage Error**, handling exitcode and stderror.```csharp
using Cysharp.Diagnostics; // using namespace// async iterate.
await foreach (string item in ProcessX.StartAsync("dotnet --info"))
{
Console.WriteLine(item);
}// receive string result from stdout.
var version = await ProcessX.StartAsync("dotnet --version").FirstAsync();// receive buffered result(similar as WaitForExit).
string[] result = await ProcessX.StartAsync("dotnet --info").ToTask();// like the shell exec, write all data to console.
await ProcessX.StartAsync("dotnet --info").WriteLineAllAsync();// consume all result and wait complete asynchronously(useful to use no result process).
await ProcessX.StartAsync("cmd /c mkdir foo").WaitAsync();// when ExitCode is not 0 or StandardError is exists, throws ProcessErrorException
try
{
await foreach (var item in ProcessX.StartAsync("dotnet --foo --bar")) { }
}
catch (ProcessErrorException ex)
{
// int .ExitCode
// string[] .ErrorOutput
Console.WriteLine(ex.ToString());
}
```Cancellation
---
to Cancel, you can use `WithCancellation` of IAsyncEnumerable.```csharp
// when cancel has been called and process still exists, call process kill before exit.
await foreach (var item in ProcessX.StartAsync("dotnet --info").WithCancellation(cancellationToken))
{
Console.WriteLine(item);
}
```timeout, you can use `CancellationTokenSource(delay)`.
```csharp
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1)))
{
await foreach (var item in ProcessX.StartAsync("dotnet --info").WithCancellation(cts.Token))
{
Console.WriteLine(item);
}
}
```Raw Process/StdError Stream
---
In default, when stdError is used, buffering error messages and throws `ProcessErrorException` with error messages after process exited. If you want to use stdError in streaming or avoid throws error when process using stderror as progress, diagnostics, you can use `GetDualAsyncEnumerable` method. Also `GetDualAsyncEnumerable` can get raw `Process`, you can use `ProcessID`, `StandardInput` etc.```csharp
// first argument is Process, if you want to know ProcessID, use StandardInput, use it.
var (_, stdOut, stdError) = ProcessX.GetDualAsyncEnumerable("dotnet --foo --bar");var consumeStdOut = Task.Run(async () =>
{
await foreach (var item in stdOut)
{
Console.WriteLine("STDOUT: " + item);
}
});var errorBuffered = new List();
var consumeStdError = Task.Run(async () =>
{
await foreach (var item in stdError)
{
Console.WriteLine("STDERROR: " + item);
errorBuffered.Add(item);
}
});try
{
await Task.WhenAll(consumeStdOut, consumeStdError);
}
catch (ProcessErrorException ex)
{
// stdout iterator throws exception when exitcode is not 0.
Console.WriteLine("ERROR, ExitCode: " + ex.ExitCode);// ex.ErrorOutput is empty, if you want to use it, buffer yourself.
// Console.WriteLine(string.Join(Environment.NewLine, errorBuffered));
}
```Read Binary Data
---
If stdout is binary data, you can use `StartReadBinaryAsync` to read `byte[]`.```csharp
byte[] bin = await ProcessX.StartReadBinaryAsync($"...");
```Change acceptable exit codes
---
In default, ExitCode is not 0 throws ProcessErrorException. You can change acceptable exit codes globally by `ProcessX.AcceptableExitCodes` property. Default is `[0]`.Zx
---
like the [google/zx](https://github.com/google/zx), you can write shell script in C#.```csharp
// ProcessX and C# 9.0 Top level statement; like google/zx.using Zx;
using static Zx.Env;// `await string` execute process like shell
await "cat package.json | grep name";// receive result msg of stdout
var branch = await "git branch --show-current";
await $"dep deploy --branch={branch}";// parallel request (similar as Task.WhenAll)
await new[]
{
"echo 1",
"echo 2",
"echo 3",
};// you can also use cd(chdir)
await "cd ../../";// run with $"" automatically escaped and quoted
var dir = "foo/foo bar";
await run($"mkdir {dir}"); // mkdir "/foo/foo bar"// helper for Console.WriteLine and colorize
log("red log.", ConsoleColor.Red);
using (color(ConsoleColor.Blue))
{
log("blue log");
Console.WriteLine("also blue");
await run($"echo {"blue blue blue"}");
}// helper for web request
var text = await fetchText("http://wttr.in");
log(text);// helper for ReadLine(stdin)
var bear = await question("What kind of bear is best?");
log($"You answered: {bear}");// run has some variant(run2, runl, withTimeout, withCancellation)
// runl returns string[](runlist -> runl)
var sdks = await runl($"dotnet --list-sdks");
```writing shell script in C# has advantage over bash/cmd/PowerShell
* Static typed
* async/await
* Code formatter
* Clean syntax via C#
* Powerful editor environment(Visual Studio/Code/Rider)`Zx.Env` has configure property and utility methods, we recommend to use via `using static Zx.Env;`.
```csharp
using Zx;
using static Zx.Env;// Env.verbose, write all stdout/stderror log to console. default is true.
verbose = false;// Env.shell, default is Windows -> "cmd /c", Linux -> "(which bash) -c";.
shell = "/bin/sh -c";// Env.terminateToken, CancellationToken that triggered by SIGTERM(Ctrl + C).
var token = terminateToken;// Env.fetch(string requestUri), request HTTP/1, return is HttpResponseMessage.
var resp = await fetch("http://wttr.in");
if (resp.IsSuccessStatusCode)
{
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}// Env.fetchText(string requestUri), request HTTP/1, return is string.
var text = await fetchText("http://wttr.in");
Console.WriteLine(text);// Env.sleep(int seconds|TimeSpan timeSpan), wrapper of Task.Delay.
await sleep(5); // wait 5 seconds// Env.withTimeout(string command, int seconds|TimeSpan timeSpan), execute process with timeout. Require to use with "$".
await withTimeout($"echo foo", 10);// Env.withCancellation(string command, CancellationToken cancellationToken), execute process with cancellation. Require to use with "$".
await withCancellation($"echo foo", terminateToken);// Env.run(FormattableString), automatically escaped and quoted. argument string requires to use with "$"
await run($"mkdir {dir}");// Env.run(FormattableString), automatically escaped and quoted. argument string requires to use with "$"
await run($"mkdir {dir}");// Env.runl(FormattableString), returns string[], automatically escaped and quoted. argument string requires to use with "$"
var l1 = runl("dotnet --list-sdks");// Env.process(string command), same as `await string` but returns Task.
var t = process("dotnet info");// Env.processl(string command), returns Task.
var l2 = processl("dotnet --list-sdks");// Env.ignore(Task), ignore ProcessErrorException
await ignore(run($"dotnet noinfo"));// ***2 receives tuple of result (StdOut, StdError).
var (stdout, stderror) = run2($"");
var (stdout, stderror) = runl2($"");
var (stdout, stderror) = withTimeout2($"");
var (stdout, stderror) = withCancellation2($"");
var (stdout, stderror) = process2($"");
var (stdout, stderror) = processl2($"");
````await string` does not escape argument so recommend to use `run($"string")` when use with argument.
If you want to more colorize like Chalk on JavaScript, [Cysharp/Kokuban](https://github.com/Cysharp/Kokuban) styler for .NET ConsoleApp will help.
Reference
---
`ProcessX.StartAsync` overloads, you can set workingDirectory, environmentVariable, encoding.```csharp
// return ProcessAsyncEnumerable
StartAsync(string command, string? workingDirectory = null, IDictionary? environmentVariable = null, Encoding? encoding = null)
StartAsync(string fileName, string? arguments, string? workingDirectory = null, IDictionary? environmentVariable = null, Encoding? encoding = null)
StartAsync(ProcessStartInfo processStartInfo)// return (Process, ProcessAsyncEnumerable, ProcessAsyncEnumerable)
GetDualAsyncEnumerable(string command, string? workingDirectory = null, IDictionary? environmentVariable = null, Encoding? encoding = null)
GetDualAsyncEnumerable(string fileName, string? arguments, string? workingDirectory = null, IDictionary? environmentVariable = null, Encoding? encoding = null)
GetDualAsyncEnumerable(ProcessStartInfo processStartInfo)// return Task
StartReadBinaryAsync(string command, string? workingDirectory = null, IDictionary? environmentVariable = null, Encoding? encoding = null)
StartReadBinaryAsync(string fileName, string? arguments, string? workingDirectory = null, IDictionary? environmentVariable = null, Encoding? encoding = null)
StartReadBinaryAsync(ProcessStartInfo processStartInfo)// return Task ;get the first result(if empty, throws exception) and wait completed
FirstAsync(CancellationToken cancellationToken = default)// return Task ;get the first result(if empty, returns null) and wait completed
FirstOrDefaultAsync(CancellationToken cancellationToken = default)// return Task
WaitAsync(CancellationToken cancellationToken = default)// return Task
ToTask(CancellationToken cancellationToken = default)// return Task
WriteLineAllAsync(CancellationToken cancellationToken = default)
```Competitor
---
* [Tyrrrz/CliWrap](https://github.com/Tyrrrz/CliWrap) - Wrapper for command line interfaces.
* [jamesmanning/RunProcessAsTask](https://github.com/jamesmanning/RunProcessAsTask) - Simple wrapper around System.Diagnostics.Process to expose it as a System.Threading.Tasks.Task.
* [mayuki/Chell](https://github.com/mayuki/Chell) Write scripts with the power of C# and .NET.License
---
This library is under the MIT License.