Command Line Parser on .NET5

Alexandre Zollinger Chohfi

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:

Adding 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.csMain 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!

Automatic help creation

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:

Not a lion

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.