Parsing Command Line Arguments

If you’ve ever had to write a console application, you’ve probably always just quickly hacked together a command line parser. This is fine when you are the only person who is using the application, because you implicitly understand that the inputfile should be declared after the outputfile. Try explaining this wisdom to your colleague who just blew away their config file they had been working on for the last couple of hours beause they got the arguments the wrong way around 🙂 Or, even worse, WYouWroteThisCode syndrome, where you realise the incompetent programmer who put the parameters this weird way around was you several years ago.

Fortunately, CSharpOptParse comes to the rescue. If you’ve ever played with Perl (who hasn’t?) it’s based on GetOpt, one of the best command line processing libraries in existance. Basically, you define a really simple object to store all your paramers (Very simple, comments axed for brevity on web):

public class Arguments
{
   
public string InputFile { get; set; }
   
public string OutputFile { get; set; }
}

Then by using attributes, you define how to map the command line on to each of these parameters:

[ShortOptionName(‘i’)]
[
LongOptionName(“input-file”)]
[
UseNameAsLongOption(false)]
[
OptDef(OptValType.ValueReq)]
[
Category(“Files”)]
[
Description(“The input file to process.”)]
public string InputFile { get; set; }

So pretty straight forward right? Then all you need to do is the following magic to parse the parameters:

Arguments arguments = new Arguments();
Parser p = ParserFactory.BuildParser(arguments);
// Parse the args
args = p.Parse(args);

The parse takes all kinds of other options, such as to use – (unix) style or / (windows) style for specifying parameters. It also supports populating items from the enivronment if they are not specified on the command line – cool 🙂 Oh, and if you want to suppot multiple options like I do in TestListGenerator, all you need to do is use a StringCollection, and tell the Parser about it via attributes:[ShortOptionName(‘i’)]
[
LongOptionName(“include”)]
[
UseNameAsLongOption(false)]
[
OptDef(OptValType.MultValue, ValueType = typeof(string))]
[
Description(“A list of categories that should be included in the test list generated. NB: You can define multiple categories by repeating the parameter – see examples.”)]
[
Category(“Criteria”)]
public StringCollection IncludedCategories = new StringCollection();

NB: Note the gotchya – you need to make sure the StringCollection is instantiated.As a little bonus, there is also a really easy way to generate usage information (works like an XmlTextWriter):UsageBuilder usage = new UsageBuilder();
usage.GroupOptionsByCategory =
true;
usage.BeginSection(“Name”);
usage.AddParagraph(
“tlg.exe  – Test List Generator for Visual Studio”);
usage.EndSection();

// Generate the list of arguments and descriptions automagically
usage.BeginSection(“Arguments”);
usage.AddOptions(p);
usage.EndSection();

So there you have it – a simple but elegant solution to your command line woes. Slowly but surely, your code will become less arcane – you’ll thank your past self later.

Post a comment or leave a trackback: Trackback URL.

Comments

  • Alex  On April 23, 2008 at 2:31 am

    Hi,

    This weblog helps explain the library philosophy a lot. I think the docs don’t show actual command lines examples.

    Can this library handle double dashes and equal sign separator like the Perl GetOpt library?

    For example, to implement the following command line inside the C# code:

    myprog.exe –date=20080310 –file=c:/data/file.txt –debug

    where –debug is an optional parameter taking no value and –date and –file are required arguments taking values.

    How do you test if –debug is defined?
    How would you access the date value in C#?

    From the example, would it be

    arguments.date.get()

    Thanks.

    Alex

  • Shaun McCarthy  On April 23, 2008 at 3:23 pm

    Yup – it defaults to unix style – you can set it explicitly on the parser object:

    p.OptStyle = OptStyle.Unix; // OptStyle.Windows

    To support the debug flag:

    [UseNameAsLongOption(false)]
    [ShortOptionName(‘d’)]
    [OptDef(OptValType.Flag)] <- this is the magic source
    [LongOptionName(“debug”)]
    public bool Debug = false;

    As for the date, either just keep it as a string, or parse it on the fly into another property:

    [UseNameAsLongOption(false)]
    [LongOptionName(“date”)]
    [OptDef(OptValType.ValueReq)]
    public string DateUnparsed
    {
    set
    {
    try
    {
    this.Date = DateTime.Parse(value);
    }
    catch (FormatException ex)
    {
    throw new ParseException(“Unable to parse the date value: ” + value, ex);
    }
    }
    get
    {
    return “”;
    }
    }

    public DateTime Date;

    Then you just need to go arguments.Date to get the date (nb: the above code would fail with your date as it’s not a valid DateTime.Parse format – 2008/03/10 would work fine though)

  • Jay Cornwall  On May 5, 2008 at 12:58 pm

    Thanks for this library, I’ve found it really useful.

    Here’s a small patch to allow the common case on Linux of passing arguments like this:
    myprogram –dir /tmp

    The current code sees this as incorrectly trying to mix UNIX and Windows style flags. This patch looks at the current OptStyle and allows values accordingly.

    http://www.jcornwall.me.uk/patches/csharpoptparse/CSharpOptParse.patch

  • Shaun McCarthy  On May 5, 2008 at 2:25 pm

    Credit where credit is due – I wasn’t actually the person who wrote the library – just wanted to point it out and give people a headstart with using it 🙂

    Andrew Robinson is the person you should be thanking! You can probably contribute your patch to the following sourceforge project:

    http://sourceforge.net/projects/csharpoptparse/

  • Jay Cornwall  On May 5, 2008 at 6:35 pm

    Oh, I see! I skimread your post a bit too quickly, it seems.

    I’ll give Andrew a bell. 🙂

  • David  On June 10, 2008 at 4:57 pm

    The Category and Description attributes work for you based on your highlighting above. When I take the download and try to use it, those attributes are not available to me. Maybe I am using it incorrectly. I placed the files into a directory and added a reference to the dll in my code. Is this all I need to do? I must be missing something.

    David

  • tihobrazov  On September 8, 2008 at 5:23 pm

    NConsoler is an open source library that provides command line parser functionality based on attribute metadata attached to type.
    Library is very easy to add and use in your application. NConsoler gives an ability to display help and validation messages without any line of code.

    http://nconsoler.csharpus.com/

    Example code:

    using System;
    using NConsoler;

    public class Program {
    public static void Main(params string[] args) {
    Consolery.Run(typeof(Program), args);
    }

    [Action]
    public static void Method(
    [Required] string name,
    [Optional(true)] bool flag) {
    Console.WriteLine(“name: {0}, flag: {1}”, name, flag);
    }
    }

    and use it:

    program.exe “Maxim” /-flag

  • Christian Kullmann  On November 13, 2008 at 9:44 am

    I can not find support for the attributes

    [Category(“Files”)]
    [Description(“The input file to process.”)]

    in the downloaded project. What am I missing?

    • Karsten Brondum  On June 30, 2011 at 10:04 am

      Did you ever find the category & description in the project. Struggling with the same problem. br/Karsten.

      • Shaun McCarthy  On June 30, 2011 at 10:13 pm

        Category and Description are actually from the Component Model – so all you need to do is add:

        using System.ComponentModel;

  • Dugmulkelunny  On February 17, 2009 at 1:58 pm

    neofight.wordpress.com – cooooolest domain name)))
    ————————
    internet signature: http://potet.ru/

  • suzanelyk  On February 8, 2012 at 12:02 am

    Cold comfort!

Leave a reply to Dugmulkelunny Cancel reply