C# – Parsing commands and arguments in a console app

In a console app there are two ways to get commands:

  • Command line arguments passed into your program via Main(string[] args).
  • User input from Console.ReadLine() (which you then split into a string[]).

After getting a command, you have to parse it to figure out what code to execute.

Typically commands have the following format: commandName -argumentName argumentValue. For example, take a look at this familiar git command:

git commit -m "init"
Code language: plaintext (plaintext)

This is passing the command line arguments into the git executable. In Main(string[] args), the arguments would look like this:

["commit", "-m", "init" ]
Code language: plaintext (plaintext)

The git executable has to parse this string to know that it has to execute the commit command.

In this article, I’ll show how to parse commands. First I’ll use a manual approach. Then I’ll show how to use the CommandLineParser library to do the parsing. The example code will support two commands: push and commit, like the following git commands:

git push git commit -m "added commit example"
Code language: plaintext (plaintext)

Manually parsing commands and arguments

You may want to start out by manually parsing your commands. As you add more commands and arguments, and the parsing becomes more and more tedious, you may decide to switch to using a parsing library instead. It pays to keep it simple.

Command line arguments are passed into a program into the Main method as a string array, like this:

["commit", "-m", "init" ]
Code language: plaintext (plaintext)

Using a switch statement

The following code shows how to parse the command line arguments by using a switch statement.

static void Main(string[] args) { if (args.Length == 0) { Console.WriteLine("Invalid args"); return; } var command = args[0]; switch (command) { case "push": Push(); break; case "commit" when args.Length == 3 && args[1] == "-m": Commit(args[2]); break; default: Console.WriteLine("Invalid command"); break; } } static void Push() { Console.WriteLine("Executing Push"); } static void Commit(string message) { Console.WriteLine($"Executing Commit with message: {message}"); }
Code language: C# (cs)

When I run this program with the following arguments:

commit -m "init"
Code language: plaintext (plaintext)

It outputs the following:

Executing Commit with message: init
Code language: plaintext (plaintext)

Using a dictionary

Instead of a switch statement, you can also use a dictionary. The key is the command name. The value is an Action<string[]> (a method delegate / function pointer accepting a string array).

In this example, I’m using a case insensitive dictionary so the command matching isn’t so strict (for example: the user can type any variation of “push” and it’ll match).

static void Main(string[] args) { if (args.Length == 0) { Console.WriteLine("Invalid args"); return; } var command = args[0]; if (!commandMap.ContainsKey(command)) { Console.WriteLine("Invalid command"); } commandMap[command](args.Skip(1).ToArray()); } private static readonly Dictionary<string, Action<string[]>> commandMap = new Dictionary<string, Action<string[]>>(StringComparer.InvariantCultureIgnoreCase) { [nameof(Push)] = Push, [nameof(Commit)] = Commit }; static void Push(string[] args) { Console.WriteLine("Executing Push"); } static void Commit(string[] args) { if (args.Length == 2 && args[0] == "-m") { Console.WriteLine($"Executing Commit with message: {args[1]}"); } else { Console.WriteLine("Invalid args. Expected format: commit -m <some message>"); } }
Code language: C# (cs)

When I run this program with the following arguments:

PUSH

It outputs:

Executing Push

Using CommandLineParser to parse commands and arguments

Instead of manually parsing commands and arguments, you can use a library like CommandLineParser to do the work for you. Then you can focus on what really matters: implementing the command logic.

I’ll show how to implement code that allows you use CommandLineParser to parse arguments and execute your commands like this:

static void Main(string[] args) { Parser.Default.ParseArguments<PushCommand, CommitCommand>(args) .WithParsed<ICommand>(t => t.Execute()); }
Code language: C# (cs)

Step 1 – Add the CommandLineParser nuget package

Execute the following command to install the nuget package:

Install-Package CommandLineParser
Code language: PowerShell (powershell)

Note: This is using Package Manager Console (View > Other windows > Package Manager Console).

Step 2 – Optional – Add ICommand interface

If your commands don’t have a base class or interface, then you’ll need to add a .WithParsed() for each possible command (or use MapResult()).

Instead, adding an interface allows you to only need a single .WithParsed<ICommand>.

public interface ICommand { void Execute(); }
Code language: C# (cs)

Step 3 – Add commands

First add the PushCommand and put the Verb attribute. This attribute tells CommandLineParser that when it sees “push”, it needs to create a PushCommand object.

[Verb("push", HelpText = "Save all your commits to the cloud")] public class PushCommand : ICommand { public void Execute() { Console.WriteLine("Executing Push"); } }
Code language: C# (cs)

Now add the CommitCommand. The commit command has an argument called “message” (-m for short). So add a property called Message and add the Option attribute. This attribute tells CommandLineParser how to map arguments to properties.

[Verb("commit", HelpText = "Save a code change")] public class CommitCommand : ICommand { [Option('m', "message", Required = true, HelpText = "Explain what code change you did")] public string Message { get; set; } public void Execute() { Console.WriteLine($"Executing Commit with message: {Message}"); } }
Code language: C# (cs)

Step 4 – Execute commands

Now parse the arguments by passing them to CommandLineParser as type arguments. Add all possible command types you want to support. In this example, these two command types are supported: PushCommand and CommitCommand.

static void Main(string[] args) { Parser.Default.ParseArguments<PushCommand, CommitCommand>(args) .WithParsed<ICommand>(t => t.Execute()); }
Code language: C# (cs)

When I run the program with the following arguments:

commit -m "changed code to use CommandLineParser"
Code language: plaintext (plaintext)

It outputs the following:

Executing Commit with message: changed code to use CommandLineParser
Code language: plaintext (plaintext)

Error handling

CommandLineParser handles invalid commands/arguments. For example, when I pass in “commita”, it shows the following error:

ERROR(S): Verb 'commita' is not recognized. --help Display this help screen. --version Display version information.
Code language: plaintext (plaintext)

When I pass in “commit -a “hello”, it shows me the following error:

ERROR(S): Option 'a' is unknown. Required option 'm, message' is missing. -m, --message Required. Explain what code change you did --help Display this help screen. --version Display version information.
Code language: plaintext (plaintext)

Notice that it’s displaying the HelpText I added to the Message property’s Option attribute.

The –help command

When you pass in –help, CommandLineParser will automatically show the list of commands available:

scc 1.0.0 Copyright (C) 2021 scc push Save all your commits to the cloud commit Save a code change help Display more information on a specific command. version Display version information.
Code language: plaintext (plaintext)

Notice that it’s showing the HelpText I specified on the Verb attribute for each command class.

Leave a Comment