Click here to Skip to main content
15,884,537 members
Articles / Programming Languages / C#

Lightweight C# Command Line Parser

Rate me:
Please Sign up or sign in to vote.
4.89/5 (13 votes)
19 Aug 2009CPOL6 min read 42.6K   1K   60   9
A lightweight command line parsing solution in C#.

Introduction

This lightweight implementation approach of a command line parser is small enough to fit in one C# file. It is complete enough that it covers most of the common command line parsing tasks:

  • 'Unlimited' number of Option Alias Names (e.g., for '-verbose', you can add these aliases: '-v', '/v', '/verbose', ..).
  • Supports options starting with '-' or '/'.
  • Supports option and parameter attached in one argument (e.g., -P=123 ).
  • Or as an argument pair (e.g./ -P 123).
  • Basic 'String', 'Integer', and 'Double' parameter options support.
  • Intelligently handles different number decimal separators.
  • Contains a basic usage (help) message of the available (registered) options.

but stays simple enough that it can be extended or changed.

Background

Looking for a command line parsing solution, I found that the existing approaches could be separated into two categories:

  1. Very simple or specialized solutions, or
  2. rather heavy weight solutions.

I missed a lightweight approach that is complete enough as a ready to use solution for quick jobs and extensible enough that it provides a solid base for extensions and changes.

The reason for more complete solutions to have the tendency to be heavy weight come from the complexity of parameter type conversion. Although it is very tempting to try to provide a solution for all different object types for 'option parameters', a better and more modern approach is to use 'method injection' to make it easy to create and add your own custom options.

An example

C#
//create parser
CMDLineParser parser = new CMDLineParser();

//add Options to parse
CMDLineParser.Option DebugOpt = 
  parser.AddBoolSwitch("-Debug", "Print Debug information");

//add an alias
DebugOpt.AddAlias("/Debug");

CMDLineParser.NumberOption NumOpt = parser.AddDoubleParameter("-NegNum=", 
                                    "A required negativ Number", true);

try
{
  //parse the command line
  parser.Parse(args);
}
catch (CMDLineParser.CMDLineParseException ex)
{
  //show available options      
  Console.Write(parser.HelpMessage());
  Console.WriteLine();
  Console.WriteLine("Error: " + ex.Message);
  return;
}

//show debug information if set
if (DebugOpt.isMatched) parser.Debug();       

//replace argument list with remaining arguments
args = parser.RemainingArgs();

double num = NumOpt.Value;

In a Windows application, you do not have a console window. In that case, you may want to replace 'Console.WriteLine(..)' with 'System.Windows.Forms.MessageBox.Show(..)'.

Dealing with invalid command line arguments

The following basic 'Exceptions' can occur while parsing the command line:

MissingRequiredOptionExceptionA required option was not detected.
DuplicateOptionExceptionA duplicate option was detected: this is an exception because a duplicate would override a previously set parameter!
ParameterConversionExceptionTrying to convert an option parameter to a specified data type (e.g., double value) failed.
InvalidOptionsExceptionInvalid, not registered options have been detected.

An 'InvalidOptionsException' is only thrown when:

C#
parser.throwInvalidOptionsException = true;

It is also possible to disable the collection of invalid (not recognized) options:

C#
parser.collectInvalidOptions = false;

leaving these options in the remaining arguments list:

C#
args = parser.RemainingArgs();

For simplicity, all described exceptions can be caught using the base class CMDLineParser.CMDLineParseException, as in the above example.

Note that in a 'NumberOption' like "-Num -16.5", the second argument will not be confused with an invalid command line option but will be identified as a parameter.

For those who just want to use the solution, this is probably all you need to know.

Discussion

Command line parsing can be separated into two problem areas:

  1. Identification of options and option parameters on the command line.
  2. Handling (type conversion) of parameter values (e.g., a double number).

The first point is a core feature of every command line parser. Regarding the second point, it could be questioned if converting parameters to different types or objects is better done through sub classing or by the application itself, instead of designing it as a core part of the command line parser. For example, a useful idea is to pass the format information for a specific parameter as a second parameter and use this information to do the actual conversion in the 'application context':

-myDate 2002-9-12 -myDateFormat "yyyy-m-dd"

From a good OO design perspective, you might want to 'inject' argument checking in your application (i.e., to expose such a check method to other interfaces) instead of having it hard coded as a part of the command line parser. Therefore, a better strategy than trying to provide an (command line) 'option' solution for every possible object type, and validation possibility which makes the parser solution heavy weight, is a 'method injection' approach. It has the advantage that option parameters can be converted and validated to your own needs.

Creating and adding custom options

Custom options can be created by sub classing CMDLineParser.Option and providing an implementation of object parseValue(string parameter) which does the actual conversion and validation, as in the following example:

C#
class PastDateOption : CMDLineParser.Option
{
  //constuctor
  public PastDateOption(string name, string desription, bool required)
        : base(name, desription, typeof(DateTime), true, required) { }
  
  
  //implementation of parseValue
  public override object parseValue(string parameter)
  {
     DateTime date= System.Convert.ToDateTime(parameter);
     DateTime now = DateTime.Now;
     
     //validate
     if(date > now)
        throw new System.ArgumentException("Date: " + date + 
                  " is greater then: " + now);
     
     return date;
    }
}

Use AddOption(..) to add the new option to the parser:

C#
// create Option
PastDateOption DateOpt = 
  new PastDateOption("-Date","A date in the past",false);
// add the Option to the parser
parser.AddOption(DateOpt);

If a custom option is matched on the command line, parseValue(..) will automatically be called to convert the option parameter and set the option value. In cases of error, it will throw a ParameterConversionException.

Note that in the example above, the date convert function will react dependent on the international settings of your local system.

Globalization issues

For good reason, most of the conversion functions built in C# provide the possibility to pass format information. Making use of a generic approach like Convert.ChangeType(parameter, type) to convert all kinds of parameter data types is tempting, but has the implication that the run time result depends on the international settings of your local system. For numbers, we can provide a more intelligent solution.

Because two different decimal separators are defined in different 'Cultures', converting numbers from the command line makes it necessary to specifically set the 'number format' expected to be passed as a command line parameter when using:

C#
Double.Parse(parameter, _numberstyle, _numberformat);

For the default NumberOption, I intentionally did not make use of the local system setting using the 'CurrentCulture' because it introduces an unnecessary globalization dependency. I remember many years ago (before C#!), how annoying it was if my command line parser did not understand a scientifically computed number (i.e., with "."); eventually, I figured out that it was reacting to my international settings (i.e., it expected ","). A better approach is to use the so called 'invariant Culture' as a default setting:

C#
_numberformat = (new CultureInfo("", false)).NumberFormat;

This setting expects a decimal point "." as a decimal separator (similar to a pocket calculator). However, approximately 50% of the Culture settings (that use Arabic numbers) use a comma "," as a decimal separator symbol.

By simply changing the 'decimal separator' to the separator found in the parameter argument, we can easily provide an implementation that accepts both formats ('.' and ','). This works well as long as NumberStyles.AllowThousands is turned off when parsing numbers. Taking into account that it is quite uncommon to use floating point numbers with 'Thousands' separator on the command line, accepting both formats ('.' and ',') is a more 'Culture Invariant' solution.

In some cases, parsing a specific (globalized) number format is desired. In that case, NumberFormatInfo can be set by creating the NumberOption:

C#
NumberFormatInfo numberformat= (new CultureInfo( "de-DE", false )).NumberFormat;

CMDLineParser.NumberOption NegnumOpt = 
   parser.AddDoubleParameter("-Num", "A Number", 
                             false, numberformat);

Conclusion

Parsing command line arguments is as old a requirement as the existence of console applications. This lightweight, 'one file' implementation aims to support projects or situations in which a lightweight implementation is sufficient.

References and credits

The article Automatic Command Line Parsing in C# by Ray Hayes influenced the creation of this article, so I would like to mention it here. It focuses on automatically creating command line options from class objects via Reflection.

In the Java world, the 'jargs project' has a similar 'lightweight' approach (with a focus on GNU compatible options).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Germany Germany
In my free time I enjoy playing African percussion.

Comments and Discussions

 
QuestionSource code does not contain CMDLINE parser class! Pin
Mike DiRenzo27-Oct-17 8:18
Mike DiRenzo27-Oct-17 8:18 
PraiseThank you Pin
Yevgeni Zolotko16-Apr-16 12:58
Yevgeni Zolotko16-Apr-16 12:58 
QuestionNot so bad but... Pin
masterluke20-Jan-13 8:22
masterluke20-Jan-13 8:22 
I found really nice solution for parsing command parameters. This solution uses extension method and linq. You just parse parameters implementing something similar to:

C#
args.Process(
               () => Console.WriteLine("Usage is switch1=value1,value2 switch2=value3"),
               new CommandLine.Switch("switch1",
                   val => Console.WriteLine("switch 1 with value {0}",
                       string.Join(" ", val))),
               new CommandLine.Switch("switch2",
                   val => Console.WriteLine("switch 2 with value {0}",
                       string.Join(" ", val)), "s1"));
       }


where args are command line arguments as string[].
You can find more details at my blog
GeneralMy vote of 5 Pin
JunfengGuo17-Mar-11 15:01
JunfengGuo17-Mar-11 15:01 
GeneralNConsoler Pin
Dima Pasko14-Oct-09 8:50
Dima Pasko14-Oct-09 8:50 
GeneralRe: NConsoler Pin
cbolti (Christian Bolterauer)15-Oct-09 2:58
cbolti (Christian Bolterauer)15-Oct-09 2:58 
GeneralNice start Pin
John Whitmire25-Aug-09 4:36
professionalJohn Whitmire25-Aug-09 4:36 
GeneralRe: Nice start Pin
cbolti (Christian Bolterauer)25-Aug-09 23:24
cbolti (Christian Bolterauer)25-Aug-09 23:24 
GeneralRe: Nice start Pin
John Whitmire26-Aug-09 6:23
professionalJohn Whitmire26-Aug-09 6:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.