Write scripts with the power of C# and .NET.
Chell is a library and execution tool for providing a shell script-like (bash, cmd, ...) experience to .NET applications.
varbranch=awaitRun($"git branch --show-current");awaitRun($"git archive {branch} -o {branch}.zip");.NET applications are great for complex tasks, but executing processes can be boring. Chell brings the experience closer to shell scripting. This library is heavily influenced by google/zx.
- Write better shell scripts: Write a complex script and use the power of .NET and C#
- Write multi-platform shell scripts: As an alternative to scripts that work on multiple platforms
- Run a process quickly in your app: As .NET library for easy handling of process launch and output
- All developers in the project are .NET developers: 🙃
Of course, if the shell script is already working fine and you don't have any problems, then there is no need to use Chell.
Using Chell makes the code feel more like a script by taking advantage of C# 9's top-level statements and C# 6's using static.
// Chell.Exports exposes a variety of functions and propertiesusingChell;usingstaticChell.Exports;// Move the current directory with Cd methodCd("/tmp");// Dispose the return value of Cd method to return to the previous directoryusing(Cd("/usr/local/bin")){// The current directory is "/usr/local/bin".}// The current directory is "/" again.// You can run the process by passing a string to Run methodawaitRun($"ls -lFa");// An interpolated string passed to Run method will be escaped and expanded if it is an arrayvarnewDirs=new[]{"foo","bar","my app","your;app"};awaitRun($"mkdir {newDirs}");// $ mkdir foo bar "my app" "your;app"// Run method returns the result object of the command (ProcessOutput class)varresult=awaitRun($"ls -lFa");// You can read stdout & stderr line by lineforeach(varlineinresult){Echo(line);}// Allows to get stdout & stderr with implicit conversion to `string`stringoutput=result;// You can also get stdout as bytes (ReadOnlyMemory<byte>)varbinary=result.OutputBinary;// Provides convenient extension methods for parsing JSON.varimages=awaitRun($"docker image ls --format {"{{json .}}"}").SuppressConsoleOutputs();foreach(varimageinimages.AsJsonLines(new{Repository="",ID="",Tag=""})){Echo(image);}// $ docker image ls --format "{{json .}}"// { Repository = mcr.microsoft.com/dotnet/sdk, ID = b160c8f3dbd6, Tag = 5.0 }// { Repository = <none>, ID = 3ee645b4a3bd, Tag = <none> }// Standard input/output of process tasks can be connected by pipesawait(Run($"ls -lFa")|Run($"grep dotnet"));// The difference with `await (Run($"ls -lFa | grep dotnet"));` is that the shell can pipe or not.// You can also specify a Stream as input or output// Write ffmpeg output to a Stream.await(Run($"ffmpeg ...")|destinationStream);// Write a Stream to ffmpeg process.await(srcStream|Run($"ffmpeg ..."));Just want to make it easy for your app to handle processes? If you don't use Chell.Exports, you won't get any unnecessary methods or properties, and you'll get the same functions by new Run(...).
usingChell;varresult=awaitnewRun($"ls -lF");Want to run it like a scripting language? Install Chell.Run, and you can run it like a script.
% dotnet tool install -g Chell.Run
% chell -e "Echo(DateTime.Now)"
9/1/2021 0:00:00 PM
% cat <<__EOF__ > MyScript.csvar dirs = new [] { "foo bar", "baz" };await Run($"mkdir {dirs}");await Run($"ls -l");__EOF__
% chell MyScript.cs
$ mkdir "foo bar""baz"
$ ls -l
total 8
drwxr-xr-x 2 mayuki mayuki 4096 Sep 1 00:00 baz/
drwxr-xr-x 2 mayuki mayuki 4096 Sep 1 00:00 'foo bar'/- Automatic shell character escaping and array expansion
- Stream and Process Pipes
- Provide utilities and shortcuts useful for scripting.
- Simple shell script-like execution tools
- Multi-platform (Windows, Linux, macOS)
- LINQPad friendly
dotnet package add Chell
.NET Standard 2.1, .NET 5 or higher
Chell.Exports class exposes a variety of utilities and shortcuts to make writing feel like shell scripting. It is recommended to include this class in your scripts with static using.
Starts a process using the specified command-line and returns a ProcessTask.
awaitRun($"ls -lF");// The followings are equivalent to calling Run methodawait(Run)$"ls -lF";awaitnewRun($"ls -lF");The process will be launched asynchronously and can wait for completion by await. And you can await to get a ProcessOutput object with its output.
If the exit code of the process returns non-zero, it will throw an exception. To suppress this exception, see NoThrow.
An interpolated string passed to Run method will be escaped and expanded if it is an array.
varnewDirs=new[]{"foo","bar","my app","your;app"};awaitRun($"mkdir {newDirs}");// equivalent to `mkdir foo bar "my app" "your;app"`You can also pass an execution options (ProcessTaskOptions) to Run method.
awaitRun($"ping -t localhost",newProcessTaskOptions(workingDirectory:@"C:\Windows",timeout:TimeSpan.FromSeconds(1)));Cd("/usr/local/bin");// equivalent to `Environment.CurrentDirectory = "/usr/local/bin";`Dispose the return value of Cd method to return to the previous directory.
Cd("/");// The current directory is "/".using(Cd("/usr/local/bin")){// The current directory is "/usr/local/bin".}// The current directory is "/" again.Same as mkdir -p. Creates a new directory and any necessary sub-directories in the specified path.
Formats the object and write it to the console.
Dump(new{Foo=123,Bar="Baz"});// => "{ Foo = 123, Bar = "Baz" }"Returns a path of the specified command.
vardotnetPath=Which("dotnet");awaitRun($"{dotnetPath} run");Echo method is equivalent to Console.WriteLine.
Echo("Hello World!");// equivalent to Console.WriteLine("Hello World!");Returns a Task that waits for the specified duration.
awaitSleep(10);// Sleep for 10 seconds.Terminates the application with an exit code.
Exit(1);Exposes the environment variables as IDictionary<string, string>.
Env.Vars["PATH"]=Env.Vars["PATH"]+":/path/to/";Returns whether the running operating system is Windows or not. If it returns false, the operating system is Linux or macOS.
if(Env.IsWindows){/* Something to do for Windows */}Specify explicitly which shell to use, or set to not use a shell.
Env.Shell.UseBash();Env.Shell.NoUseShell();Env.Shell.UseCmd();Sets or gets the output level when executing a command/process.
Verbosity.All: Displays both the command line and the output of the commandVerbosity.CommandLine: Displays the command lineVerbosity.Output: Displays the output of the commandVerbosity.Silent: No display
Sets the timeout for running the process. The default value is 0 (disabled).
Env.ProcessTimeout=TimeSpan.FromSecond(1);// OperationCanceledException will be thrown after 1s.awaitRun($"ping -t localhost");Gets the arguments passed to the current application.
// $ myapp foo bar baz => new [] { "foo", "bar", "baz" };foreach(vararginArguments){/* ... */}Gets the current directory and the application directory or name or path.
// C:\> cd C:\Users\Alice// C:\Users\Alice> Downloads\MyApp.exeEcho(CurrentDirectory);// C:\Users\AliceEcho(ExecutableDirectory);// C:\Users\Alice\DownloadsEcho(ExecutableName);// MyApp.exeEcho(ExecutablePath);// C:\Users\Alice\Downloads\MyApp.exeGets the path of the current user's home directory.
// Windows: C:/Users/<UserName>// Linux: /home/<UserName>// macOS: /Users/<UserName>Echo(HomeDirectory);Provides the wrapper with methods useful for reading and writing to the standard input/output/error streams.
// Reads data from standard input.awaitStdIn.ReadToEndAsync();// Writes data to standard output or error.StdOut.WriteLine("FooBar");StdErr.WriteLine("Oops!");Represents the execution task of the process started by Run.
Connects the standard output of the process to another ProcessTask or Stream.
await(Run($"ls -lF")|Run($"grep .dll"));// The followings are equivalent to using '|'.varprocTask1=Run($"ls -lF");varprocTask2=Run($"grep .dll");procTask1.Pipe(procTask2);A Stream can also be passed to Pipe. If the ProcessTask has connected to the Stream, it will not write to ProcessOutput.
varmemStream=newMemoryStream();awaitRun($"ls -lF").Pipe(memStream);Connects the Stream to the standard input of the process. The method can be called only once before the process starts.
await(myStream|Run($"grep .dll"));// The followings are equivalent to using '|'.varprocTask=Run($"grep .dll");procTask.ConnectStreamToStandardInput(myStream);Suppresses exception throwing when the exit code is non-zero.
awaitRun($"AppReturnsExitCodeNonZero").NoThrow();Suppresses the writing of command execution results to the standard output.
// equivalent to "Env.Verbosity = Verbosity.Silent" or pipe to null.awaitRun($"ls -lF").SuppressConsoleOutputs();Returns a Task to get the exit code of the process. This is equivalent to waiting for a ProcessTask with NoThrow.
varproc=Run($"ls -lF");if(awaitproc.ExitCode!=0){
...}// equivalent to `(await Run($"ls -lF").NoThrow()).ExitCode`Provides the results of the process execution.
Gets the combined standard output and error as a string or byte array.
Gets the standard output as a string or byte array.
Gets the standard error as a string or byte array.
Gets the combined standard output and error as a per-line IEnumerable<string>.
// equivalent to `foreach (var line in procOutput.AsLines())`foreach(varlineinprocOutput){ ...}The method equivalent to Combined property.
Gets the exit code of the process.
Chell.Exports class also exposes a variety of useful utilities and shortcuts to libraries.
Prompts the user for input and gets it.
varname=awaitPrompt("What's your name? ");Provides a shortcut to mayuki/Kokuban. You can easily style the text on the terminal.
// "Error: " will be colored.Echo((Chalk.Red+"Error: ")+"Something went wrong.");Provides a shortcut to Microsoft.Extensions.FileSystemGlobbing.
Glob(params string[] patterns)Glob(string baseDir, string[] patterns)
// Glob patterns starting with '!' will be treated as excludes.foreach(varpathinGlob("**/*.cs","!**/*.vb")){
...}Provides shortcuts to System.Text.Json.
ToJson<T>(T obj)
varobj=new{Name="Alice",Age=18};varjson=ToJson(obj);Echo(json);// {"Name":"Alice","Age":18}FromJson<T>(string json)FromJson<T>(string json, T shape)
varjson="{ \"foo\": 123 }";varobj=FromJson(json,new{Foo=0});Dump(obj);// { Foo = 123 }AsJsonAsJsonLines
usingChell;varoutput=awaitRun($"docker image ls --format {"{{json .}}"}");foreach(varimageinoutput.AsJsonLines(new{Repository="",ID="",Tag=""})){// ...}usingChell;varoutput=awaitRun($"kubectl version --client -o json");varobj=output.AsJson(new{clientVersion=new{major="",minor="",gitVersion=""}});Echo(obj);// { clientVersion = { major = 1, minor = 21, gitVersion = v1.21.2 } }Provides shortcuts to System.Net.Http.HttpClient.
FetchAsyncFetchByteArrayAsyncFetchStreamAsyncFetchStringAsync
Chell can also be used as a utility library to run processes.
If you don't use Chell.Exports, you won't get any unnecessary methods or properties, and you can use Run and ChellEnvironment, Exports class.
usingChell;varresults=awaitnewRun($"ls -lF");// ChellEnvironment.Current is equivalent to `Env` on `Chell.Exports`.Console.WriteLine(ChellEnvironment.Current.ExecutablePath);Console.WriteLine(ChellEnvironment.Current.ExecutableName);Console.WriteLine(ChellEnvironment.Current.Arguments);Console.WriteLine(ChellEnvironment.Current.Vars["PATH"]);Chell.Run executes the input source code in an environment where Chell and some libraries are available.
It does not perform any NuGet package resolution, so we recommend creating a typical C# project if you need to handle such complexities.
$ dotnet tool install -g Chell.Run
$ chell -e "Echo(123);"
$ chell <<EOFvar result = await Run($"ls -lF");Echo("Hello World!");EOFChell.Run implicitly has some namespace imports and library references and can be used out-of-the-box.
Implicitly specified usings:
- System
- System.Collections
- System.Collections.Generic
- System.Diagnostics
- System.IO
- System.Text
- System.Text.RegularExpressions
- System.Linq
- System.Threading
- System.Threading.Tasks
- Chell
- Chell.Extensions
- Chell.Exports (using static)
Additional referenced libraries:
- Mono.Options
- Sharprompt
- Cocona.Lite
LINQPad is often used to do tasks in place of shell scripts. Chell can help you in this case as well.
Chell knows about LINQPad, so Dump method is replaced by LINQPad's Dump, and standard output and errors are displayed in the results without problems.
MIT License
Copyright © Mayuki Sawatari <mayuki@misuzilla.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
