Command Line Parser on .NET5

Alexandre

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.

8 comments

Comments are closed. Login to edit/delete your existing comments

  • Mark Dell

    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.

    • Alexandre Zollinger ChohfiMicrosoft employee

      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 fit your needs.
      By the way, I’ve found an interesting discussion on the System.CommandLine GitHub on why this behavior exists: https://github.com/dotnet/command-line-api/issues/354, which I believe is the same reason this issue exists on CommandLineParser.

  • Ramon de Klein

    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.

  • Alexander Trauzzi

    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.

  • Phil Bolduc

    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 be different option sets that are required or mutually exclusive. For example an app that makes http requests could have a ‘–url’ parameter, or “–host and –port” parameters. A ‘git push’, you can only optionally have flag ‘–all’ or ‘–mirror’ or ‘–tags’. I have also seen apps that dynamically scan for “Verbs” in the app at start up so you dont have to change the Main(string[] args) function. Thank you for this post. Is a good start. Perhaps a follow up on how one would create some basic git command line type app would help.

  • Haymo Kutschbach

    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 ?!