Command Line Parsing

12 February 2010

From time to time, I am still asked to create a console application for someone, and most of the time PowerShell is still not accepted by my clients for various reasons. Just like method parameters can become unwieldy in an under-designed system as requirements change, so can command line arguments. To minimize the mess, regular expressions can be used to access a command/arguments pattern (e.g. EncryptionUtil.exe /Decrypt /File="C:\My Documents\foo.xml"). Furthermore, if argument are passed in as a key/value pairing (e.g. /File="C:\My Documents\foo.xml"), the order of the arguments should not matter.

Determining the Command

 1 /// <summary>
 2 /// Parses command line arguments to find the "command" 
 3 /// that should be executed.
 4 /// </summary>
 5 /// <param name="args">The command line arguments.</param>
 6 /// <returns>
 7 /// The command that is expected to be executed. 
 8 /// Note: The value has been set to lower case (Invariant). 
 9 /// </returns>
10 /// <exception cref="TooManyCommandsException">
11 /// Throws and exception if more than one command is found.
12 /// </exception>
13 static string GetArgumentCommand(string[] args)
14 {
15   const string regex = "^/([^/][^=]+?)$";
16   var reg = new Regex(regex);
17 
18   string result = null;
19 
20   //Loop through arguments looking for mataches to regex.
21   foreach (var arg in args)
22   {
23     //Argument didn't match, continue to next arg.
24     if (!reg.IsMatch(arg))
25       continue; 
26 
27     //More than one argument matched. Throw an error.
28     if (!string.IsNullOrEmpty(result))
29       throw new TooManyCommandsException();
30 
31     //Argument matched  
32     var match = reg.Match(arg);
33     
34     //Check for null/empty
35     var validMatch = match.Groups.Count >= 2 
36       && match.Groups[1].Value != null;
37     
38     result = validMatch ?
39       match.Groups[1].Value.ToLowerInvariant() : 
40       null;
41   }
42 
43 return result;
44 }

Determining the Arguments

 1 /// <summary>
 2 /// Parses command line arguments and creates a dictionary
 3 /// of key/value pair arguments.
 4 /// Note: If a key is used more than once, the first one is used.
 5 /// </summary>
 6 /// <param name="args">The command line arguments.</param>
 7 /// <returns>
 8 /// Dictionary containing arguements.
 9 /// Note: All keys have been set to lower case (Invariant). 
10 /// </returns>
11 static Dictionary&lt;string, string&gt; GetArgumentDictionary(string[] args)
12 {
13   const string regex = "^/([^/][\\S]+?)\\=([\\S\\s]+?)$";
14   var reg = new Regex(regex);
15 
16   var dic = new Dictionary&lt;string, string&gt;();
17 
18   //Loop through arguments looking for mataches to regex.
19   foreach (var arg in args)
20   {
21     //Argument didn't match, continue to next arg.
22     if (!reg.IsMatch(arg))
23       continue;
24 
25 
26     //Argument matched
27     var match = reg.Match(arg);
28 
29     //Determining Key
30     var validMatch = match.Groups.Count >= 3 
31       && match.Groups[1].Value != null;
32     var key = validMatch ? 
33       match.Groups[1].Value.ToLowerInvariant() : 
34       null;
35     if (string.IsNullOrEmpty(key))
36       continue;
37 
38     //Check for duplicate key.
39     if (dic.ContainsKey(key))
40       continue;
41 
42     //Determine value
43     validMatch = match.Groups.Count >= 3;
44     var val = validMatch ? match.Groups[2].Value : null;
45 
46     //Add key/value pair to dictionary.
47     dic.Add(key, val);
48   }
49 
50   return dic;
51 }

Putting it Together

 1 /// <summary>
 2 /// Application entry point.
 3 /// </summary>
 4 /// <param name="args">The command line arguments.</param>
 5 static void Main(string[] args)
 6 {
 7   try
 8   {
 9     //Get the command and dictionary of arguments.
10     var cmd = GetArgumentCommand(args);
11     var dic = GetArgumentDictionary(args);
12 
13     //Execute appropriate command.
14     switch (cmd) //remember commands are lower case
15     {
16       case "foo":
17         ExecuteFooCommand(dic);
18         break;
19       case "bar":
20         ExecuteBarCommand(dic);
21         break;
22       case "?": //redundant, but makes the point.
23       default:
24         DisplayHelpText(dic);
25         break;
26     }
27   }
28   catch (Exception ex)
29   {
30     Console.WriteLine("An exception occured.");
31     Console.WriteLine(ex.Message);
32     Console.WriteLine(ex.StackTrace);
33   }
34 }
35 
36 
37 /// <summary>
38 /// Executes the foo command.
39 /// </summary>
40 /// <param name="dic">Dictionary containing key/value arguements.</param>
41 static void ExecuteFooCommand(Dictionary&lt;string, string&gt; dic)
42 {
43   //Add appropriate code here.
44 }
45 
46 /// <summary>
47 /// Executes the bar command.
48 /// </summary>
49 /// <param name="dic">Dictionary containing key/value arguements.</param>
50 static void ExecuteBarCommand(Dictionary&lt;string, string&gt; dic)
51 {
52   //Add appropriate code here.
53 }
54 
55 /// <summary>
56 /// Displays help text to the console.
57 /// </summary>
58 /// <param name="dic">Dictionary containing key/value arguements.</param>
59 static void DisplayHelpText(Dictionary&lt;string, string&gt; dic)
60 {
61   //Add appropriate code here.
62 }