Command Line Apps
Today we are going to show you how to get started parsing Command Line arguments, following on from our series on .NET 5
.
Command Line Applications, also known as Console Applications, are programs built to be used from a shell, such as cmd or bash. They have been around since the 1960’s, way before Windows, MacOS, or any other graphical user interface (GUI) came to be.
Usually, when you start learning a programing language, the simplest and most common sample is widely known as a Hello world
app. These samples pretty much only print the text “Hello world” on the console, using their built-in APIs. Computer software can do a lot of different things. Sometimes you will have an input which is, somehow, translated to an output. Our Hello world
sample doesn’t have any input.
Lets take C#/.Net
. Whenever you create a new console app, you start with a Program.cs
file with a static Main
method that is the entry point of your application:
...
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
...
A very important part of this code is the string[] args
definition. That is the parameter definition that contains all the arguments that are passed down to our executable during the initialization of our process. Unlike C and C++, the name of the program is not treated as the first command-line argument in the args array. If you want that value, you can call the Environment.GetCommandLineArgs()
.
If you are used to command-line apps, passing arguments to other apps is a very common task. Yes, you can manually parse those values, but once you have multiple parameters it can be a very error-prone code (which is mostly boilerplate anyway). This seems like a problem that someone else might have fixed already, right? So of course we can find a NuGet library that helps us parse these arguments. For this blog post, I’ll focus on CommandLineParser.
CommandLineParser
The CommandLineParser
is an open-source library built by Eric Newton and members of the .NET community. It’s been around since 2005 and it has more than 26 million downloads! The CommandLineParser
“offers CLR applications a clean and concise API for manipulating command line arguments and related tasks, such as defining switches, options and verb commands”.
Instead of manually parsing the args string array, you can simply define a class that will be parsed for you by the library, based on a set of attributes that you annotate the class with.
Instead of creating yet another sample just for the sake of showing this library, I’ll use the WinML .NET5 console app that I’ve shared on my last blog post. Here is the source code. Lets start with it and add the CommandLineParser
NuGet package:
Lets create a new class named CommandLineOptions
:
using CommandLine;
namespace ImageClassifier
{
public class CommandLineOptions
{
[Value(index: 0, Required = true, HelpText = "Image file Path to analyze.")]
public string Path { get; set; }
[Option(shortName: 'c', longName: "confidence", Required = false, HelpText = "Minimum confidence.", Default = 0.9f)]
public float Confidence { get; set; }
}
}
This is pretty much everything we need to use this library. The ValueAttribute
and OptionAttribute
are both provided by the package. I’m using named parameters to make it very clear what each argument is for. Back to our Program.cs
‘ Main
method, lets add the using
statement to be able to easily use the package’s classes in this file:
using CommandLine;
Lets change the return type of our Main
method to be a Task<int>
. It means that whichever int
value we return will be returned to the caller of our process, which usually indicates success/failure. In this example, we’ll simply return 0
on success and something other than 0
on errors:
static async Task<int> Main(string[] args)
{
return await Parser.Default.ParseArguments<CommandLineOptions>(args)
.MapResult(async (CommandLineOptions opts) =>
{
try
{
// We have the parsed arguments, so let's just pass them down
return await AnalyzeFileAsync(opts.Path, opts.Confidence);
}
catch
{
Console.WriteLine("Error!");
return -3; // Unhandled error
}
},
errs => Task.FromResult(-1)); // Invalid arguments
}
You can see all the changes from the previous version of the code here.
With these changes in place the app gracefully parses our arguments. There is even a help page generated automatically for us!
So lets say you want to analyze an image, but you want its result even if you are not too sure about it, lets say a confidence of 30%. That can easily be done now using the -c
(of the long name --confidence
) argument. With this image:
You can get this result when using the --confidence
:
> .ImageClassifier.exe C:\Users\alzollin\Downloads\NotALion.jpg --confidence 0.3
Image 'C:\Users\alzollin\Downloads\NotALion.jpg' is classified as 'Persian cat'(p=58%).
Simple, right?
Conclusion
The CommandLineParser
NuGet package is a very powerful helper that simplifies this common repetitive task into a simple declarative approach. Also, it is much more customizable than what I’ve demonstrated here. You can find it’s documentation at their GitHub’s Wiki page.
Thank you! I wonder if this could open up a way to get around the 8192 character limit when providing long parameters to Windows command line apps? The only ‘solution’ I found / was advised to so far is to shorten the parameters, which is kind of odd ?!
This is nice :),
but I still prefer good old GOLD Parsing System , Multi-Programming Language, Parser.
that wold be : " ...parsing system that you can use to develop your own programming languages, scripting languages and interpreters.".
With this piece of tech, I can easily write/invent new command line language,
with new commands followed by necessary syntax, save it as grammar *.egt file,
and by using GOLD Engine.dll and its public classes for loading grammar and...
This post only scratches the surface of features. I would like to see a series of blog posts that talk about the more advanced features required in most command line apps. For example, base the examples on command line parsing to simulate tools we use every day like git or dotnet tool. These apps use verb/command syntax and the parameters for each verb/command vary. Also for a given verb/command there can...
I just looked up how the dotnet utility parses it’s command line. It uses the System.CommandLine package – https://github.com/dotnet/command-line-api. Another thing for me to review.
This seems like a very abbreviated approach to creating commands if it's to be considered as technical advice. Many important concerns are unanswered -- concerns that the software community at large has already dealt with in much better ways.
I would suggest anyone reading take a look at the CliFx library which has done a great job to go beyond simple parameter parsing.
This is a library Microsoft should look into lending some support to instead.
I really don’t like the attribute-based setup, because it’s often too limit. Use a fluent-style commandline parser and it will be much easier and allow for more complex scenarios. I used https://github.com/spf13/cobra for Go and it’s very powerful and easy to use.
I like the syscommand library, which I created, it simulates a Mvc and does not need almost any code.
https://github.com/juniorgasparotto/SysCommand
I hope this does NOT use the Microsoft recommended method of command line parsing where a quote preceded by a back slash is not treated as a quote character.
Even though those recommendations are old, it goes against preexisting behaviour that has been around for decades.
It also means that a back slash is sometimes treated as an escape character which is horribly inconsistent!
That recommended behaviour significantly complicates usage for the average end user.
Hello Mark. First, thanks for reading!
CommandLineParser is one option we have in the .NET ecosystem, and I chose to write about it since it is community driven, highly successful, and widely adopted. If you are having issues with it, please, report it at their Github page. I'm sure that the community would be more than happy to help you. Another option I would also recommend is using the System.CommandLine, which is much newer and might...