Managing Command Line Arguments

Published 26 June 08 01:40 PM

I've been spending many of my recent developer cycles writing console applications to perform various tasks in an ETL process.  And yes, before you ask, I had started out initially modeling my ETL process with SSIS - and while I could eventually get it to do what I wanted, at the end of the day, regular code was a more direct and expressive syntax for describing my process.

Anyway, one of the recurring things that I found myself having to do as I was writing these little command line apps was parsing various command line arguments - whether they were positional parameters, optional (named) parameters or switches.  I refactored that args-managing logic out into its own class and wanted to share it with you in the case that you find yourself rewriting similar code in your applications.  One of the things that you'll notice is that the error handling code is pretty sparse, and I could have made it even more robust by supporting different arg formats instead of hard-coding the delimiter characters.  Perhaps one day...

internal class CommandLineArgs
{
    private readonly IEnumerable<string> _args;

    public CommandLineArgs(IEnumerable<string> args) {
        _args = args;
    }

    public bool GetSwitch(string switchName) {
        var swt =
            _args.FirstOrDefault(
                a =>
                a.Equals("/" + switchName, StringComparison.InvariantCultureIgnoreCase) ||
                a.Equals("-" + switchName, StringComparison.InvariantCultureIgnoreCase));
        return !string.IsNullOrEmpty(swt);
    }

    public T ParseParam<T>(string param) {
        var paramString = _args.FirstOrDefault(s => s.StartsWith(string.Format("/{0}:", param)));

        if (string.IsNullOrEmpty(paramString))
            throw new ArgumentException(
                "The requested argument was not found in the command line arguments.", param);

        var paramValueString = paramString.Split(':')[1];
        return GetTypedVal<T>(paramValueString);
    }

    public T GetArgAt<T>(int position) {
        var val = _args.ElementAt(position);
        return GetTypedVal<T>(val);
    }

    private T GetTypedVal<T>(string val) {
        try {
            return (T) Convert.ChangeType(val, typeof (T));
        }
        catch (Exception ex) {
            throw new ArgumentException(
                "Argument value [" + val + "] could not be parsed into type [" + typeof (T).Name + "].",
                ex);
        }
    }
}

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Arjan`s World &raquo; LINKBLOG for June 28, 2008 said on June 28, 2008 4:00 PM:

PingBack from http://www.arjansworld.com/2008/06/28/linkblog-for-june-28-2008/

# OJ said on June 28, 2008 6:01 PM:

Hi,

It's definitely worth having a class like this handy for when you need to crank out yet another command-line-driven application. I'd like to make a couple of comments about this class though (from an API/reuse perspective).

* GetSwitch() doesn't actually get the switch. This would probably be better if you named it HasSwitch() or ContainsSwitch() or something similar.

* ParseParam() is another function that could do with a name change. On the surface it'd make much more sense to call it GetSwitchParameter() or something like that. ParseParam() doesn't really indicate that you're extracting the value from a switch parameter - particularly not in a typed fashion.

Naming functions in such a manner which indicates what they're actually doing is very beneficial, particularly if other people are going to be using your code. You might always want to mention that this code requires .NET 3.5.

Thanks for listening. Nice post :)

Cheers

OJ

# hdierking said on June 29, 2008 12:52 AM:

OJ -

totally agree with "HasSwitch" - that's a much more clear expression of intent.

also agree with the need to rename "ParseParam" - but to avoid potential confusion between params and switches, I would settle more on a name like "GetParamValue"

just one "ctrl+shift+r" -> "Rename" and problem solved :)

thanks!

# Aaron Murrell said on July 1, 2008 6:44 PM:

I have actually seen Robert C. Martin give super presentations on refactoring using the example of a command line parsing framework.  Pretty fun to watch him if you ever get the chance.  I don't think I've seen his code for it online - but I think yours is at LEAST as good as his :)

# Aaron Murrell said on July 1, 2008 6:45 PM:

hah!  found it...  Good stuff:

http://www.objectmentor.com/resources/articles/Clean_Code_Args.pdf

Leave a Comment

(required) 
(optional)
(required) 

About hdierking

I am currently the Editor-in-Chief for MSDN Magazine. I joined Microsoft in 2006 as a product planner with the certification team at Microsoft Learning. Prior to that, I spent my career as a developer and later as an architect. My main technology passions include pretty much anything on language theory, agile development, and service-oriented architecture.
Page view tracker