{"id":13677159,"url":"https://github.com/Cysharp/ProcessX","last_synced_at":"2025-04-29T10:32:05.093Z","repository":{"id":37783263,"uuid":"236994231","full_name":"Cysharp/ProcessX","owner":"Cysharp","description":"Simplify call an external process with the async streams in C# 8.0.","archived":false,"fork":false,"pushed_at":"2025-03-19T06:40:12.000Z","size":82,"stargazers_count":488,"open_issues_count":3,"forks_count":32,"subscribers_count":19,"default_branch":"master","last_synced_at":"2025-04-12T13:57:33.414Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Cysharp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-01-29T13:57:00.000Z","updated_at":"2025-04-05T00:39:48.000Z","dependencies_parsed_at":"2024-01-06T16:10:43.051Z","dependency_job_id":"6150cce1-8fe0-417c-8f62-6d2adef0f1ad","html_url":"https://github.com/Cysharp/ProcessX","commit_stats":{"total_commits":74,"total_committers":6,"mean_commits":"12.333333333333334","dds":"0.43243243243243246","last_synced_commit":"c700b2dbcfce2cdbfba4aa7ea43e46322756ccc2"},"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cysharp%2FProcessX","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cysharp%2FProcessX/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cysharp%2FProcessX/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cysharp%2FProcessX/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Cysharp","download_url":"https://codeload.github.com/Cysharp/ProcessX/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251484003,"owners_count":21596639,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T13:00:37.754Z","updated_at":"2025-04-29T10:32:00.081Z","avatar_url":"https://github.com/Cysharp.png","language":"C#","readme":"[![GitHub Actions](https://github.com/Cysharp/ProcessX/workflows/Build-Debug/badge.svg)](https://github.com/Cysharp/ProcessX/actions)\n\nProcessX\n===\n\nProcessX 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.\n\n![image](https://user-images.githubusercontent.com/46207/73369038-504f0c80-42f5-11ea-8b36-5c5c979ac882.png)\n\nAlso provides zx mode to write shell script in C#, details see [Zx](#zx) section.\n\n![image](https://user-images.githubusercontent.com/46207/130373766-0f16e9ad-57ba-446b-81ee-c255c7149035.png)\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n## Table of Contents\n\n- [Getting Started](#getting-started)\n- [Cancellation](#cancellation)\n- [Raw Process/StdError Stream](#raw-processstderror-stream)\n- [Read Binary Data](#read-binary-data)\n- [Change acceptable exit codes](#change-acceptable-exit-codes)\n- [Zx](#zx)\n- [Reference](#reference)\n- [Competitor](#competitor)\n- [License](#license)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\nGetting Started\n---\nInstall library from NuGet that support from `.NET Standard 2.0`.\n\n\u003e PM\u003e Install-Package [ProcessX](https://www.nuget.org/packages/ProcessX)\n\nMain API is only `Cysharp.Diagnostics.ProcessX.StartAsync` and throws `ProcessErrorException` when error detected.\n\n* **Simple**, only write single string command like the shell script.\n* **Asynchronous**, by C# 8.0 async streams.\n* **Manage Error**, handling exitcode and stderror.\n\n```csharp\nusing Cysharp.Diagnostics; // using namespace\n\n// async iterate.\nawait foreach (string item in ProcessX.StartAsync(\"dotnet --info\"))\n{\n    Console.WriteLine(item);\n}\n\n// receive string result from stdout.\nvar version = await ProcessX.StartAsync(\"dotnet --version\").FirstAsync();\n\n// receive buffered result(similar as WaitForExit).\nstring[] result = await ProcessX.StartAsync(\"dotnet --info\").ToTask();\n\n// like the shell exec, write all data to console.\nawait ProcessX.StartAsync(\"dotnet --info\").WriteLineAllAsync();\n\n// consume all result and wait complete asynchronously(useful to use no result process).\nawait ProcessX.StartAsync(\"cmd /c mkdir foo\").WaitAsync();\n\n// when ExitCode is not 0 or StandardError is exists, throws ProcessErrorException\ntry\n{\n    await foreach (var item in ProcessX.StartAsync(\"dotnet --foo --bar\")) { }\n}\ncatch (ProcessErrorException ex)\n{\n    // int .ExitCode\n    // string[] .ErrorOutput\n    Console.WriteLine(ex.ToString());\n}\n```\n\nCancellation\n---\nto Cancel, you can use `WithCancellation` of IAsyncEnumerable.\n\n```csharp\n// when cancel has been called and process still exists, call process kill before exit.\nawait foreach (var item in ProcessX.StartAsync(\"dotnet --info\").WithCancellation(cancellationToken))\n{\n    Console.WriteLine(item);\n}\n```\n\ntimeout, you can use `CancellationTokenSource(delay)`.\n\n```csharp\nusing (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1)))\n{\n    await foreach (var item in ProcessX.StartAsync(\"dotnet --info\").WithCancellation(cts.Token))\n    {\n        Console.WriteLine(item);\n    }\n}\n```\n\nRaw Process/StdError Stream\n---\nIn 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.\n\n```csharp\n// first argument is Process, if you want to know ProcessID, use StandardInput, use it.\nvar (_, stdOut, stdError) = ProcessX.GetDualAsyncEnumerable(\"dotnet --foo --bar\");\n\nvar consumeStdOut = Task.Run(async () =\u003e\n{\n    await foreach (var item in stdOut)\n    {\n        Console.WriteLine(\"STDOUT: \" + item);\n    }\n});\n\nvar errorBuffered = new List\u003cstring\u003e();\nvar consumeStdError = Task.Run(async () =\u003e\n{\n    await foreach (var item in stdError)\n    {\n        Console.WriteLine(\"STDERROR: \" + item);\n        errorBuffered.Add(item);\n    }\n});\n\ntry\n{\n    await Task.WhenAll(consumeStdOut, consumeStdError);\n}\ncatch (ProcessErrorException ex)\n{\n    // stdout iterator throws exception when exitcode is not 0.\n    Console.WriteLine(\"ERROR, ExitCode: \" + ex.ExitCode);\n\n    // ex.ErrorOutput is empty, if you want to use it, buffer yourself.\n    // Console.WriteLine(string.Join(Environment.NewLine, errorBuffered));\n}\n```\n\nRead Binary Data\n---\nIf stdout is binary data, you can use `StartReadBinaryAsync` to read `byte[]`.\n\n```csharp\nbyte[] bin = await ProcessX.StartReadBinaryAsync($\"...\");\n```\n\nChange acceptable exit codes\n---\nIn default, ExitCode is not 0 throws ProcessErrorException. You can change acceptable exit codes globally by `ProcessX.AcceptableExitCodes` property. Default is `[0]`.\n\nZx\n---\nlike the [google/zx](https://github.com/google/zx), you can write shell script in C#.\n\n```csharp\n// ProcessX and C# 9.0 Top level statement; like google/zx.\n\nusing Zx;\nusing static Zx.Env;\n\n// `await string` execute process like shell\nawait \"cat package.json | grep name\";\n\n// receive result msg of stdout\nvar branch = await \"git branch --show-current\";\nawait $\"dep deploy --branch={branch}\";\n\n// parallel request (similar as Task.WhenAll)\nawait new[]\n{\n    \"echo 1\",\n    \"echo 2\",\n    \"echo 3\",\n};\n\n// you can also use cd(chdir)\nawait \"cd ../../\";\n\n// run with $\"\" automatically escaped and quoted\nvar dir = \"foo/foo bar\";\nawait run($\"mkdir {dir}\"); // mkdir \"/foo/foo bar\"\n\n// helper for Console.WriteLine and colorize\nlog(\"red log.\", ConsoleColor.Red);\nusing (color(ConsoleColor.Blue))\n{\n    log(\"blue log\");\n    Console.WriteLine(\"also blue\");\n    await run($\"echo {\"blue blue blue\"}\");\n}\n\n// helper for web request\nvar text = await fetchText(\"http://wttr.in\");\nlog(text);\n\n// helper for ReadLine(stdin)\nvar bear = await question(\"What kind of bear is best?\");\nlog($\"You answered: {bear}\");\n\n// run has some variant(run2, runl, withTimeout, withCancellation)\n// runl returns string[](runlist -\u003e runl)\nvar sdks = await runl($\"dotnet --list-sdks\");\n```\n\nwriting shell script in C# has advantage over bash/cmd/PowerShell\n\n* Static typed\n* async/await\n* Code formatter\n* Clean syntax via C#\n* Powerful editor environment(Visual Studio/Code/Rider)\n\n`Zx.Env` has configure property and utility methods, we recommend to use via `using static Zx.Env;`.\n\n```csharp\nusing Zx;\nusing static Zx.Env;\n\n// Env.verbose, write all stdout/stderror log to console. default is true.\nverbose = false;\n\n// Env.shell, default is Windows -\u003e \"cmd /c\", Linux -\u003e \"(which bash) -c\";.\nshell = \"/bin/sh -c\";\n\n// Env.terminateToken, CancellationToken that triggered by SIGTERM(Ctrl + C).\nvar token = terminateToken;\n\n// Env.fetch(string requestUri), request HTTP/1, return is HttpResponseMessage.\nvar resp = await fetch(\"http://wttr.in\");\nif (resp.IsSuccessStatusCode)\n{\n    Console.WriteLine(await resp.Content.ReadAsStringAsync());\n}\n\n// Env.fetchText(string requestUri), request HTTP/1, return is string.\nvar text = await fetchText(\"http://wttr.in\");\nConsole.WriteLine(text);\n\n// Env.sleep(int seconds|TimeSpan timeSpan), wrapper of Task.Delay.\nawait sleep(5); // wait 5 seconds\n\n// Env.withTimeout(string command, int seconds|TimeSpan timeSpan), execute process with timeout. Require to use with \"$\".\nawait withTimeout($\"echo foo\", 10);\n\n// Env.withCancellation(string command, CancellationToken cancellationToken), execute process with cancellation. Require to use with \"$\".\nawait withCancellation($\"echo foo\", terminateToken);\n\n// Env.run(FormattableString), automatically escaped and quoted. argument string requires to use with \"$\"\nawait run($\"mkdir {dir}\");\n\n// Env.run(FormattableString), automatically escaped and quoted. argument string requires to use with \"$\"\nawait run($\"mkdir {dir}\");\n\n// Env.runl(FormattableString), returns string[], automatically escaped and quoted. argument string requires to use with \"$\"\nvar l1 = runl(\"dotnet --list-sdks\");\n\n// Env.process(string command), same as `await string` but returns Task\u003cstring\u003e.\nvar t = process(\"dotnet info\");\n\n// Env.processl(string command), returns Task\u003cstring[]\u003e.\nvar l2 = processl(\"dotnet --list-sdks\");\n\n// Env.ignore(Task), ignore ProcessErrorException\nawait ignore(run($\"dotnet noinfo\"));\n\n// ***2 receives tuple of result (StdOut, StdError).\nvar (stdout, stderror) = run2($\"\");\nvar (stdout, stderror) = runl2($\"\");\nvar (stdout, stderror) = withTimeout2($\"\");\nvar (stdout, stderror) = withCancellation2($\"\");\nvar (stdout, stderror) = process2($\"\");\nvar (stdout, stderror) = processl2($\"\");\n```\n\n`await string` does not escape argument so recommend to use `run($\"string\")` when use with argument.\n\nIf you want to more colorize like Chalk on JavaScript, [Cysharp/Kokuban](https://github.com/Cysharp/Kokuban) styler for .NET ConsoleApp will help.\n\nReference\n---\n`ProcessX.StartAsync` overloads, you can set workingDirectory, environmentVariable, encoding.\n\n```csharp\n// return ProcessAsyncEnumerable\nStartAsync(string command, string? workingDirectory = null, IDictionary\u003cstring, string\u003e? environmentVariable = null, Encoding? encoding = null)\nStartAsync(string fileName, string? arguments, string? workingDirectory = null, IDictionary\u003cstring, string\u003e? environmentVariable = null, Encoding? encoding = null)\nStartAsync(ProcessStartInfo processStartInfo)\n\n// return (Process, ProcessAsyncEnumerable, ProcessAsyncEnumerable)\nGetDualAsyncEnumerable(string command, string? workingDirectory = null, IDictionary\u003cstring, string\u003e? environmentVariable = null, Encoding? encoding = null)\nGetDualAsyncEnumerable(string fileName, string? arguments, string? workingDirectory = null, IDictionary\u003cstring, string\u003e? environmentVariable = null, Encoding? encoding = null)\nGetDualAsyncEnumerable(ProcessStartInfo processStartInfo)\n\n// return Task\u003cbyte[]\u003e\nStartReadBinaryAsync(string command, string? workingDirectory = null, IDictionary\u003cstring, string\u003e? environmentVariable = null, Encoding? encoding = null)\nStartReadBinaryAsync(string fileName, string? arguments, string? workingDirectory = null, IDictionary\u003cstring, string\u003e? environmentVariable = null, Encoding? encoding = null)\nStartReadBinaryAsync(ProcessStartInfo processStartInfo)\n\n// return Task\u003cstring\u003e ;get the first result(if empty, throws exception) and wait completed\nFirstAsync(CancellationToken cancellationToken = default)\n\n// return Task\u003cstring?\u003e ;get the first result(if empty, returns null) and wait completed\nFirstOrDefaultAsync(CancellationToken cancellationToken = default)\n\n// return Task\nWaitAsync(CancellationToken cancellationToken = default)\n\n// return Task\u003cstring[]\u003e\nToTask(CancellationToken cancellationToken = default)\n\n// return Task\nWriteLineAllAsync(CancellationToken cancellationToken = default)\n```\n\nCompetitor\n---\n* [Tyrrrz/CliWrap](https://github.com/Tyrrrz/CliWrap) - Wrapper for command line interfaces.\n* [jamesmanning/RunProcessAsTask](https://github.com/jamesmanning/RunProcessAsTask) - Simple wrapper around System.Diagnostics.Process to expose it as a System.Threading.Tasks.Task.\n* [mayuki/Chell](https://github.com/mayuki/Chell) Write scripts with the power of C# and .NET.\n\nLicense\n---\nThis library is under the MIT License.\n","funding_links":[],"categories":["C#"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCysharp%2FProcessX","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FCysharp%2FProcessX","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCysharp%2FProcessX/lists"}