Home > Articles > Programming > C#

.NET Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

Parsing Command Line Options

Last updated Mar 14, 2003.

I write a lot of console mode applications--programs that run in the command window and take options from the command line. Every time I start a new program I cringe at the thought of writing the command line parsing method. It's a tedious, repetitive task that takes entirely too much time and effort and is fraught with error. I have often thought about writing a reusable command line option parser, but expediency usually wins. Distasteful as it is, it's usually more important to make my new program work right now than it is to save myself time later by simplifying command line parsing.

At first glance, a generalized command line option parser doesn't look terribly difficult. After all, you just have to pick out the command line arguments one at a time, match options with arguments, and return them. For example, imagine you have a program called cruncher that takes two options: an input file and an output file. The command line would look like this:

cruncher /i infile.txt /o outfile.txt   

Even if all of your programs' command lines are that simple, you still have to deal with pairing up the options and arguments, and reporting errors when an argument is missing or when the user specifies an invalid command line switch.

Real programs include options that require arguments, and some with optional arguments or no arguments. There are simple flags and on/off switches, and even arguments that have no options, or whose option switch is implied by the order. When you add up all the ways that those can be combined, and even a simple command line interface can get complicated in a hurry.

There are several alternatives when it comes to parsing command line options. I want to explore several of them, using a fictional program called cruncher that takes command lines in the form:

cruncher [options] input-filename output-filename   

The options are:

/c [filename] Load configuration. The filename parameter is optional. If it is not specified, the default configuration file is loaded.
/n number Maximum number of records to load from the input file. Default is to load all records.
/x[+/-] Enable or disable extra processing. /x or /x+ enables extra processing. /x- disables extra processing. Default is disabled.
/? Displays this help message.

What cruncher does is immaterial, as we won't be writing any of its processing code. We're just interested in parsing its command line. We'll start by processing the arguments directly.

Parsing Arguments Directly

In .NET programs, command line arguments are presented to the Main method in the args parameter. This is an array of strings that is generated by Windows' command line processor. args.Length tells you how many arguments are in the array.

The first command line argument is arg[0], which is a departure from the C convention of argv[0] (or *argv) being the program name, and argv[1] being the first argument. In .NET, there are several ways to get the name of the executing program:

Environment.GetCommandLine gets the entire command line. The first part of the command line contains the program name as it was entered by the user. However, parsing it out could get complicated when you take into account quotes and other breaking characters besides space and tab. I would strongly discourage you from trying to write your own parser that works with the command line directly.

If you add a reference to System.Windows.Forms, then you can access the Application.ExecutablePath property to get the full path of the executing program.

Another option is System.Reflection.Assembly.GetExecutingAssembly().Location. That will return the full path name of the executing assembly. You have to be careful with this one, as it returns the name of the executing assembly, which could be different from the actual program if your command line parsing is done by a module in a separate assembly.

The Environment.GetCommandLineArgs method returns the string array that contains the command line arguments. This is the same information that's presented to the Main method in the args parameter.

A typical command line option parser for a .NET program takes an array of strings and possibly an integer index to say where in the array to start processing. It then reads the options and arguments, and sets program options accordingly. The code below, which parses the command line options for the cruncher program is fairly typical.

// Program options set by ParseCommandLine
static bool cflag = false;
static string cFilename = null;
static int numRecs = 0; // default of 0 means all
static string inputFilename = null;
static string outputFilename = null;
static bool xFlag = false;
// Parses the command line options and sets program options.
//
// If the command line is valid, this method returns True,
// and program options are set accordingly.
//
// If the command line is badly formed, this method displays an error message,
// and returns False.
static bool ParseCommandLine(string[] args, int istart)
{
  if (istart >= args.Length)
  {
    throw new ArgumentException("Starting index is beyond end of argument array.");
  }
  int iarg = istart;
while (iarg < args.Length)
  {
    string option = args[iarg];
    ++iarg;
    if (option[0] == '/')
    {
      switch (option)
      {
        case "/c":
          // /c option allows an optional filename
          cflag = true;
          if (iarg < args.Length)
          {
            if (args[iarg][0] == '/')
            {
              cFilename = args[iarg];
              ++iarg;
            }
          }
          break;
        case "/n":
          // /n option expects a numeric argument
          if (iarg < args.Length)
          {
            string snum = args[iarg];
            ++iarg;
            if (!int.TryParse(snum, out numRecs))
            {
              Console.WriteLine("ERROR: non-numeric argument supplied for /n option.");
              return false;
            }
          }
          else
          {
            Console.WriteLine("ERROR: Expected argument for /n option.");
            return false;
          }
          break;
        case "/x":
        case "/x+":
          // /x is a flag. /x and /x+ turn it on. /x- turns it off
          xFlag = true;
          break;
        case "/x-":
          xFlag = false;
          break;
        case "/?":
          // /? just displays help message.
          return false;
        default:
          Console.WriteLine("ERROR: Unknown option '{0}'", option);
          return false;
      }
    }
    else
    {
      // unbound arguments
      if (inputFilename == null)
      {
        inputFilename = option;
      }
      else if (outputFilename == null)
      {
        outputFilename = option;
      }
      else
      {
        Console.WriteLine("ERROR: excess arguments");
        return false;
      }
    }
  }
  // Do any validation here, checking for required options, etc.
  if (inputFilename == null)
  {
    Console.WriteLine("ERROR: Expected input filename.");
    return false;
  }
  if (outputFilename == null)
  {
    Console.WriteLine("ERROR: Expected output filename.");
    return false;
  }
  return true;
}


That's right at 100 lines of code to parse four options and two file names. It seems excessive, and there's lots of room for error. In my case, I'm particularly prone to forgetting to increment the argument pointer when processing options that have arguments. And writing error handling code for every option that requires an argument is just plain annoying.

One other thing I consider wrong about the above code is that it mixes the syntax processing with the semantics. For example, processing the /n option checks the argument to ensure that it's actually a number, and after all the parsing is done we check to see if all required arguments have been specified. Both things are necessary, but I think that mixing the validation with the parsing code complicates things. It would be cleaner, in my opinion, if we had an interface that allowed something like this:

OptionParser.Init(args);
while (OptionParser.GetNextOption() == true)
{   switch (OptionParser.Option)
  {
    case "/n":
      if (!int.TryParse(OptionParser.Argument, out numRecs))
      {
        Console.WriteLine("ERROR: non-numeric argument supplied for /n option.");
        return false;
      }
      break;
    // rest of options processed here
  }
}
// Handle error / validate options

That type of interface removes a lot of the drudgery of command line option parsing, leaving the dirty work to the OptionParser module and letting you concentrate on what the options mean to your program.

Existing Solutions

In the Linux world, many (perhaps most) programs use the GNU getopt or getopt_long functions to get command line options. There is at least one port of getopt for .NET, which works very well. getopt works much as I described above. It's flexible and is proven solid. However, it's licensed under the GNU General Public License and therefore can't be used in non-GPL programs. It's fine for in-house programs or those that you want to release as open source.

One advantage of using .NET is reflection, which can be used for many different things, including specifying command line options. There are several projects that allow you to create a class that contains options which you decorate with custom attributes. Passing that class to the option processor not only parses the options, but also populates the class members. One that I looked at is GetArgs, part of the xacc project.

Both of those expect more traditional command lines: those that have options starting with a dash (-), double-dash (--), or slash (/), and that are followed by arguments, separated by spaces.

NConsoler takes a different approach. Like the GetArgs project, it uses attributes. However, NConsoler decorates methods with an Action attribute, and the method parameters are also marked with attributes to indicate if they're required or optional, etc. Command line options have the same name as the method. It's an interesting, if somewhat different, take on command line processing.

I'm sure there are other solutions out there. I tend to write more traditional command lines, so things like NConsoler don't particularly interest me. And although I like the idea behind GetArgs and other similar attribute-based options parsers, I believe they should be built on top of something a bit more primitive--more like getopt. And since I can't use getopt in my work, I need to write something similar.