Parsing command line arguments with C# & LINQ

Whenever I sit down to write an application (mostly a console application), I find myself wishing I had some code I could just drop in to parse command line arguments with. A friend of mine has authored the excellent ConsoleFX library, and it’s really good if you want to do heavy processing, validations and complex command line argument structures with help. Most of the time, however, I don’t.

So I spent the better part of my evening writing something that I hope I will be able to carry around as a code snippet small enough to use anytime, anywhere in an elegant fashion.

  1. #region CommandLine
  2. static class CommandLine
  3. {
  4. public class Switch // Class that encapsulates switch data. Not meant for direct use.
  5. {
  6. public Switch(string name, Action<IEnumerable<string>> handler, string shortForm = null)
  7. {
  8. Name = name;
  9. Handler = handler;
  10. ShortForm = shortForm;
  11. }
  12. public string Name
  13. {
  14. get;
  15. private set;
  16. }
  17. public string ShortForm
  18. {
  19. get; private set;
  20. }
  21. public Action<IEnumerable<string>> Handler
  22. {
  23. get; private set;
  24. }
  25. public int InvokeHandler(string[] values)
  26. {
  27. Handler(values);
  28. return 1;
  29. }
  30. }
  31. // The regex that extracts names and comma-separated values for switches
  32. // in the form (<switch>[:value,value,value])+
  33. private static readonly Regex _regex =
  34. new Regex(@"\s*(?<switch>(?<name>\w+)(:\s*(?<value>\w+(\s*,\s*\w+)*))?)",
  35. RegexOptions.Compiled | RegexOptions.CultureInvariant |
  36. RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);
  37. private const string NameGroup = "name"; // Names of capture groups
  38. private const string ValueGroup = "value";
  39. public static void Process(this string[] args, Action printUsage, params Switch[] switches)
  40. {
  41. // Run through all matches in the concatenated argument list and if any
  42. // of the switches match, get the values and invoke the handler we were
  43. // given. We do a Sum() here for 2 reasons; a) To actually run the handlers
  44. // and b) see if any were invoked at all (each returns 1 if invoked).
  45. // If none were invoked, we simply invoke the printUsage handler.
  46. if ((from Match match in _regex.Matches(string.Join(" ", args))
  47. from arg in switches
  48. where match.Success &&
  49. ((string.Compare(match.Groups[NameGroup].Value, arg.Name, true) == 0) ||
  50. (string.Compare(match.Groups[NameGroup].Value, arg.ShortForm, true) == 0))
  51. select arg.InvokeHandler(match.Groups[ValueGroup].Value.Split(','))).Sum() == 0)
  52. printUsage(); // We didn't find any switches
  53. }
  54. }
  55. #endregion

All of the magic happens inside the Process method, where the query tries to match the arguments against the ones we want to check for. The .Sum() aggregate method does two things.

  1. It enumerates the elements of the IQueryable, actually calling the handlers in the process. Nothing happens till then!
  2. Each of the InvokeHandler calls returns 1, so the result of the .Sum() is 0 if there were no matches (which is when we call the supplied printUsage handler).

Neat, huh? Using it is even simpler!

  1. args.Process(
  2. () => Console.WriteLine("Usage is switch0:value switch:value switch2"),
  3. new CommandLine.Switch("switch0",
  4. val => Console.WriteLine("switch 0 with value {0}",
  5. string.Join(" ", val))),
  6. new CommandLine.Switch("switch1",
  7. val => Console.WriteLine("switch 1 with value {0}",
  8. string.Join(" ", val)), "s1"),
  9. new CommandLine.Switch("switch2",
  10. val => Console.WriteLine("switch 2 with value {0}",
  11. string.Join(" ", val))));
  12. }

Any handlers you provide will be called for each switch or it’s shortform(if found, of course). Now get started writing those fantastic console applications.

Obligatory disclaimer and License: The code above is for you to do whatever you want to. Even if it blows you up (like Dr. Manhattan blows Rorschach up), don’t come back from the dead to haunt me. I would, however appreciate it if you kept the comment attributing that code to me.

Double Edit: Modified the Regex so it can accept quoted parameters (even filenames).

Discuss

 

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Twitter Updates