DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 3.23 Parsing Command-Line Parameters

Problem

You require your applications to accept one or more command-line parameters in a standard format. You need to access and parse the entire command line passed to your application.

Solution

Use the following class to help with parsing command-line parameters:

using System;
using System.Diagnostics;

public class ParseCmdLine
{
    // All args are delimited by tab or space
    // All double-quotes are removed except when escaped '\"'
    // All single-quotes are left untouched

    public ParseCmdLine( ) {}

    public virtual string ParseSwitch(string arg)
    {
        arg = arg.TrimStart(new char[2] {'/', '-'});

        return (arg);
    }

    public virtual void ParseSwitchColonArg(string arg, out string outSwitch, 
                                            out string outArgument)
    {
        outSwitch = "";
        outArgument = "";

        try
        {
            // This is a switch or switch/argument pair
            arg = arg.TrimStart(new char[2] {'/', '-'});

            if (arg.IndexOf(':') >= 0)
            {
                outSwitch = arg.Substring(0, arg.IndexOf(':'));
                outArgument = arg.Substring(arg.IndexOf(':') + 1);

                if (outArgument.Trim( ).Length <= 0)
                {
                   throw (new ArgumentException(
                      "Command-Line parameter error: switch " +
                      arg + 
                      " must be followed by one or more arguments.", arg));
                }
            }
            else
            {
                throw (new ArgumentException(
                        "Command-Line parameter error: argument " +
                        arg + 
                        " must be in the form of a 'switch:argument}' pair.", 
                        arg));
            }
        }
        catch (ArgumentException ae)
        {
            // Re-throw the exception to be handled in the calling method
            throw;
        }
        catch (Exception e)
        {
            // Wrap an ArgumentException around the exception thrown
            throw (new ArgumentException("General command-Line parameter error", 
                                         arg, e));
        }
    }

    public virtual void ParseSwitchColonArgs(string arg, out string outSwitch, 
                                             out string[] outArguments)
    {
        outSwitch = "";
        outArguments = null;

        try
        {
            // This is a switch or switch/argument pair
            arg = arg.TrimStart(new char[2] {'/', '-'});

            if (arg.IndexOf(':') >= 0)
            {
                outSwitch = arg.Substring(0, arg.IndexOf(':'));
                string Arguments = arg.Substring(arg.IndexOf(':') + 1);

                if (Arguments.Trim( ).Length <= 0)
                {
                   throw (new ArgumentException(
                           "Command-Line parameter error: switch " +
                           arg + 
                           " must be followed by one or more arguments.", arg));
                }

                outArguments = Arguments.Split(new char[1] {';'});
            }
            else
            {
                throw (new ArgumentException(
                   "Command-Line parameter error: argument " +
                   arg + 
                   " must be in the form of a 'switch:argument{;argument}' pair.", 
                   arg));
            }
        }
        catch (Exception e)
        {
            // Wrap an ArgumentException around the exception thrown
            throw ;
        }
    }

    public virtual void DisplayErrorMsg( )
    {
        DisplayErrorMsg("");
    }

    public virtual void DisplayErrorMsg(string msg)
    {
        Console.WriteLine
            ("An error occurred while processing the command-line arguments:");
        Console.WriteLine(msg);
        Console.WriteLine( );

        FileVersionInfo version = 
                   Process.GetCurrentProcess( ).MainModule.FileVersionInfo;
        if (Process.GetCurrentProcess( ).ProcessName.Trim( ).Length > 0)
        {
            Console.WriteLine(Process.GetCurrentProcess( ).ProcessName);
        }
        else
        {
            Console.WriteLine("Product Name: " + version.ProductName);
        }

        Console.WriteLine("Version " + version.FileVersion);
        Console.WriteLine("Copyright " + version.LegalCopyright);
        Console.WriteLine("TradeMarks " + version.LegalTrademarks);

        DisplayHelp( );
    }

    public virtual void DisplayHelp( )
    {
        Console.WriteLine("See help for command-line usage.");
    }
}

Discussion

Before command-line parameters can be parsed, a common format must first be decided upon. The format for this recipe follows the command-line format for the Visual C# .NET language compiler. The format used is defined as follows:

  • All command-line arguments are separated by one or more spaces and/or tabs.

  • Each argument may start with either a - or / character, but not both. If it does not, that argument is considered a literal, such as a filename.

  • Each argument that starts with either the - or / character may be divided up into a switch followed by a colon followed by one or more arguments separated with the ; character. The command-line parameter -sw:arg1;arg2;arg3 is divided up into a switch (sw) and three arguments (arg1, arg2, and arg3). Note that there should not be any spaces in the full argument; otherwise, the runtime command-line parser will split up the argument into two or more arguments.

  • Strings delineated with double quotes, such as "c:\test\file.log" will have their double quotes stripped off. This is a function of the runtime interpreting the arguments passed in to your application.

  • Single quotes are not stripped off.

  • To preserve double quotes, precede the double quote character with the \ escape sequence character.

  • The \ character is handled only as an escape sequence character when followed by a double quote—in which case, only the double-quote is displayed.

  • The ^ character is handled by the runtime command-line parser as a special character.

Fortunately, the runtime command-line parser (for Visual Studio .NET, this would be devenv.exe) handles most of this before your application receives the individual parsed arguments.

The runtime command-line parser passes a string[] containing each parsed argument to the entry point of your application. The entry point can take one of the following forms:

public static void Main( )
public static int Main( )
public static void Main(string[] args)
public static int Main(string[] args)

The first two accept no arguments, but the last two accept the array of parsed command-line arguments. Note that the static Environment.CommandLine property will also return a string containing the entire command line and the static Environment.GetCommandLineArgs method will return an array of strings containing the parsed command-line arguments. The individual arguments in this array can then be passed to the various methods of the ParseCmdLine class. The following code shows how this can be accomplished:

[STAThread]
public static void Main(string[] args)
{
    // The application should be initialized here assuming no command-line 
    //    parameters were found.

    ParseCmdLine parse = new ParseCmdLine( );

    try
    {
        // Create an array of all possible command-line parameters 
        // and how to parse them
        object[,] mySwitches = new object[2, 4] {
                {"file", "output", "trialmode", "debugoutput"},
                {ArgType.Simple, ArgType.Compound, ArgType.SimpleSwitch, 
                 ArgType.Complex}};

        // Loop through all command-line parameters
        for (int counter = 0; counter < args.Length; counter++)
        {
            args[counter] = args[counter].TrimStart(new char[2] {'/', '-'});

            // Search for the correct ArgType and parse argument according to 
            //   this ArgType
            for (int index = 0; index <= mySwitches.GetUpperBound(1); index++)
            {
                string theSwitch;
                string theArgument;
                string[] theArguments;

                if (args[counter].StartsWith((string)mySwitches[0, index]))
                {
                    // Parse each argument into switch:arg1;arg2...
                    switch ((ArgType)mySwitches[1, index])
                    {
                        case ArgType.Simple:
                            theSwitch = args[counter];
                            break;

                        case ArgType.SimpleSwitch:
                            theSwitch = parse.ParseSwitch(args[counter]);
                            break;

                        case ArgType.Compound:
                            parse.ParseSwitchColonArg(args[counter],out theSwitch, 
                                                      out theArgument);
                            break;

                        case ArgType.Complex:
                            parse.ParseSwitchColonArgs(args[counter],out theSwitch, 
                                                       out theArguments);
                            break;

                        default:
                            throw (new ArgumentException(
                              "Cmd-Line parameter error: ArgType enumeration " + 
                              mySwitches[1, index].ToString( ) + 
                              " not recognized."));
                    }

                    // Implement functionality to handle each parsed 
                    //    command-line parameter
                    switch ((string)mySwitches[0, index])
                    {
                        case "file":
                            // Handle this switch here...
                            break;

                        case "output":
                            // Handle this switch here...
                            break;

                        case "trialmode":
                            // Handle this switch and its argument here...
                            break;

                        case "debugoutput":
                            // Handle this switch and its arguments here...
                            break;

                        default:
                            throw (new ArgumentException(
                               "Cmd-Line parameter error: Switch " + 
                               mySwitches[0, index].ToString( ) + 
                               " not recognized."));
                    }
                }
            } 
        } 
    }
    catch (ArgumentException ae)
    {
        parse.DisplayErrorMsg(ae.ToString( ));
        return;
    }
    catch (Exception e)                
    {
        // Handle other exceptions here
        // ...
    }
}

The ArgType enumeration is defined as follows:

enum ArgType
{
    Simple = 0,       // A simple file name with no preceding '/' or '-' chars
    SimpleSwitch = 1, // A switch preceded by '/' or '-' chars
    Compound = 2,     // A 'switch:argument' pair preceded by '/' or '-' chars
    Complex = 3       // A 'switch:argument{;argument}' pair with multiple args 
                      //    preceded by '/' or '-' chars
}

Passing in the following command-line arguments to this application:

MyApp c:\input\infile.txt -output:d:\outfile.txt -trialmode 
      /debugoutput:c:\test1.log;\\myserver\history\test2.log

results in the following parsed switches and arguments:

Literal:     c:\input\infile.txt

Switch:      output
Argument:    d:\outfile.txt

Switch:      trialmode

Switch:      debugoutput
Arguments:   c:\test1.log
             \\myserver\history\test2.log

If we input incorrectly formed command-line parameters, such as forgetting to add arguments to the -output switch, we get the following output:

An error has occured while processing the command-line arguments:
System.ArgumentException: Command-Line parameter error: argument output must be
in the form of a 'switch:argument{;argument}' pair.
Parameter name: output
   at Chapter_Code.ParseCmdLine.ParseSwitchColonArg(String arg, 
       String& outSwitch, String& outArgument) 
       in c:\book cs cookbook\code\chapter3.cs:line 238
   at Chapter_Code.Class1.Main(String[] args) 
       in c:\book cs cookbook\code\main.cs:line 55

CHAPTER_CODE.EXE
Version 1.0.1009.12739
Copyright
TradeMarks
See help for command-line usage.

This may be too much output to show to the user; for example, you might not want the entire exception to be displayed. In addition, the last line in the message indicates that you should see the help files for information on the correct command-line usage. It would be more useful to display the correct command-line arguments and some brief information on their usage. To do this, we can extend the ParseCmdLine class and make our own specialized class to use in our application. The following class shows how this is accomplished:

public class SpecializedParseCmdLine : ParseCmdLine
{
    public SpecializedParseCmdLine( ) {}

    public override string ParseSwitch(string arg)
    {
        if (arg.IndexOf(':') < 0)
        {
            throw (new ArgumentException("Command-Line parameter error: switch " + 
                   arg + " must not be followed by one or more arguments.", arg));
        }

        return (base.ParseSwitch(arg));
    }

    public virtual void DisplayErrorMsg( )
    {
        DisplayErrorMsg("");
    }

    public virtual void DisplayErrorMsg(string msg)
    {
        Console.WriteLine(
             "An error has occurred while processing the command-line arguments:");
        Console.WriteLine( );

        FileVersionInfo version = 
                 Process.GetCurrentProcess( ).MainModule.FileVersionInfo;
        if (Process.GetCurrentProcess( ).ProcessName.Trim( ).Length > 0)
        {
            Console.WriteLine(Process.GetCurrentProcess( ).ProcessName);
        }
        else
        {
            Console.WriteLine("Product Name: " + version.ProductName);
        }

        Console.WriteLine("Version " + version.FileVersion);
        Console.WriteLine("Copyright " + version.LegalCopyright);
        Console.WriteLine("TradeMarks " + version.LegalTrademarks);

        DisplayHelp( );
    }
    public override void DisplayHelp( )
    {
        // Display correct input args
        base.DisplayHelp( );

        Console.WriteLine("Chapter_Code [file | /output:projectfile | /trialmode | 
                           /debugoutput:file{;file}]");
        Console.WriteLine( );
        Console.WriteLine("Available command-line switches:");
        Console.WriteLine("\tfile        : The file to use as input.");
        Console.WriteLine("\toutput      : The file to use as output.");
        Console.WriteLine("\ttrialmode   : Turns on the trial mode, if present.");
        Console.WriteLine("\tdebugoutput : One or more files in which to dump 
                           debug information into.");
    }
}

This class overrides four methods of the ParseCmdLine class. The DisplayHelp method is overridden to display the relevant information needed to correctly use the command-line parameters in our application. The overloaded DisplayErrorMsg methods are overridden to prevent the lengthy exception message from being displayed. Finally, the ParseSwitch method is overridden to add some more preventative code that will disallow any arguments from being added to a switch that should not have any arguments. By overriding other methods in the ParseCmdLine class, you can modify this class to handle many other situations specific to your application.

See Also

See the "Main" and "Command-Line Arguments" topics in the MSDN documentation.

    [ Team LiB ] Previous Section Next Section