Being a developer and a geek for as long as I can remember, command line tools have always fascinated me. They are relatively easy to create and use than a web app or a desktop app.

In this post, we will explore how to build a simple command line tool on Linux using .NET Core.

Create a new console app using the dotnet cli.

$ dotnet new console -o netcore-test-cli
$ code netcore-test-cli

Most command line tools do take user input and perform actions as based on these inputs.

These inputs come into the Main function during runtime by the way of the string[] args parameter. We could simply parse the arguments and their values from the string array args, but this is a tedious thing for us to do.

Fortunately, there are many open source libraries to help us do this. One such library is CommandLineParser. I have used it in a couple of projects and like it.

Let us add this command line parsing library to the project.

$ dotnet add package CommandLineParser

To get started with CommandLineParser, first we need to specify the types and names of the arguments in the form of an Options class. Then we need to use a Parser instance to parse the arguments provided.

var result = Parser.Default.ParseArguments<Options>(args)

On successful parsing, we get back a Parsed<Options> type. Otherwise, we get back a NotParsed<T> type which is an error collection of type IEnumerable<Error>.

The prescribed way to do this is to use the WithParsed and WithNotParsed extension methods which take on the respective parsed types and pass the parsed options to further processing.

static void Main(string[] args)
{
    Parser.Default.ParseArguments<Options>(args)
        .WithParsed<Options>(options => ProcessOptions(options))
        .WithNotParsed<Options>(options => HandleErrors(options));
}

For the sake of simplicity, let us define two options, Count and Name, as follows.

class Options
{
    [Option('c', "count", HelpText="The count variable")]
    public int Count { get; set; }

    [Option(HelpText="Name for greeting")]
    public string Name { get; set; }
}

We need to specify the [Option] attribute on our two properties. This lets CommandLineParser know which properties are valid for parsing against the user input. Notice that Count option has c for its ShortName and count for its LongName. This lets you specify count input as either -c 10 or --count 10.

Let us just take the Count option to generate a sum of all whole numbers upto Count and display a exit message if the Name option is specified.

private static void ProcessOptions(Options options)
{
    int sum = 0;
    for (int i = 1; i <= options.Count; i++)
    {
        sum = sum + i;
    }
    Console.WriteLine($"Sum: {sum}");

    if(!string.IsNullOrEmpty(options.Name))
        Console.WriteLine($"Bye, {options.Name}");
}

To run the program use, dotnet run command.

$ dotnet run -- --count 10 --name=animesh
Sum: 55
Bye, animesh

If you want to run the compiled program directly, you can run dotnet command and pass the name of the output dll.

$ dotnet bin/Debug/netcoreapp2.2/netcore-test-cli.dll -c 123
Sum: 7626

To make it a proper Linux executable, we need to publish the program with appropriate runtime identifier.

$ dotnet publish -c 'release' -r 'linux-x64'

Microsoft (R) Build Engine version 15.9.20+g88f5fadfbe for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restore completed in 55.08 ms for /home/animesh/projects/lucubrations/netcore-test-cli/netcore-test-cli.csproj.
  netcore-test-cli -> /home/animesh/projects/lucubrations/netcore-test-cli/bin/release/netcoreapp2.2/linux-x64/netcore-test-cli.dll
  netcore-test-cli -> /home/animesh/projects/lucubrations/netcore-test-cli/bin/release/netcoreapp2.2/linux-x64/publish/

This publishes the program to folder bin/Release/netcoreapp2.2/linux-x64. Check it out.

$ ll bin/Release/netcoreapp2.2/linux-x64

total 1552
-rwxrw-rw- 1 animesh animesh 692128 Jan 18 03:54 libhostfxr.so
-rwxrw-rw- 1 animesh animesh 712624 Jan 18 03:54 libhostpolicy.so
-rwxrw-rw- 1 animesh animesh 106912 Mar  9 15:29 netcore-test-cli
-rw-r--r-- 1 animesh animesh  37340 Mar  9 15:29 netcore-test-cli.deps.json
-rw-r--r-- 1 animesh animesh   5632 Mar  9 15:29 netcore-test-cli.dll
-rw-r--r-- 1 animesh animesh    928 Mar  9 15:29 netcore-test-cli.pdb
-rw-r--r-- 1 animesh animesh    206 Mar  9 15:29 netcore-test-cli.runtimeconfig.dev.json
-rw-r--r-- 1 animesh animesh     26 Mar  9 15:29 netcore-test-cli.runtimeconfig.json
drwxr-xr-x 2 animesh animesh  12288 Mar  9 15:29 publish

Notice the file attributes on the third item netcore-test-cli in the directory listing above. It has file attributes -rwxrw-rw-, which means it is a Linux executable. This became possible as we specified the linux-x64 runtime identifier.

We can run the executable just as before.

$ cd bin/Release/netcoreapp2.2/linux-x64

$ ./netcore-test-cli 

Sum: 0

$ ./netcore-test-cli -c 100

Sum: 5050

Both parameters specified:

$ ./netcore-test-cli -c 1000 --name animesh

Sum: 500500
Bye, animesh

Specifying the --help option shows all possible options for user input:

$ ./netcore-test-cli --help

netcore-test-cli 1.0.0
Copyright (C) 2019 netcore-test-cli

  -c, --count    The count variable

  --name         Name for greeting

  --help         Display this help screen.

  --version      Display version information.

That's the end of this post. We have seen how to build a basic command line application with .NET Core.

All code for this post is available on my gitlab repository.