Click here to Skip to main content
15,881,172 members
Articles / Programming Languages / C#
Article

C#/.NET Command Line Arguments Parser

Rate me:
Please Sign up or sign in to vote.
4.91/5 (104 votes)
5 Nov 2002MIT3 min read 864.3K   8.5K   269   133
Class to parse command line arguments and store/retrieve them.

Introduction

Even with modern UI, we often need a way to start our programs with specific parameters. Command line arguments are helpful to provide those parameters without exposing them to everybody. When developing with .NET and C# you can get the command line arguments from your Main(string[] Args) function. Args is in fact an array containing all the strings separated by spaces entered in the command line. What the following class does is to transform this array of strings into a ready to use collection of key/value pairs. You can then easily find and get from this collection a specific value for one of your parameters (key).

The Arguments class

This class parses your command line arguments, find all parameters starting with -, -- or / and all the values linked. I assumed that a value could be separated from a parameter with a space, a : or a =. The parser also look for enclosing characters like ' or " and remove them. Of course if you have a value like 'Mike's house', only the first and last ' will be removed. To achieve its goal, the class relies heavily on the regular expressions capabilities of .NET. The first regular expression (^-{1,2}|^/|=|:) splits one argument into several parts:

  • the parameter
  • the value

This regular expression handles cases where only a parameter is present, only a value is present or if both are present. The program performs accordingly to the number of parts found. The second regular expression (^['"]?(.*?)['"]?$) is used to detect and remove all starting and trailing ' or " characters from a value. When all your arguments are parsed, retrieving a value from a parameter is as easy as writing MyValue=params["MyParam"]. If the parameter doesn't exist or was not in the command line then you will get a null reference you can test against.

C#
using System;
using System.Collections.Specialized;
using System.Text.RegularExpressions;

namespace CommandLine.Utility
{
    /// <summary>
    /// Arguments class
    /// </summary>
    public class Arguments{
        // Variables
        private StringDictionary Parameters;

        // Constructor
        public Arguments(string[] Args)
        {
            Parameters = new StringDictionary();
            Regex Spliter = new Regex(@"^-{1,2}|^/|=|:",
                RegexOptions.IgnoreCase|RegexOptions.Compiled);

            Regex Remover = new Regex(@"^['""]?(.*?)['""]?$",
                RegexOptions.IgnoreCase|RegexOptions.Compiled);

            string Parameter = null;
            string[] Parts;

            // Valid parameters forms:
            // {-,/,--}param{ ,=,:}((",')value(",'))
            // Examples: 
            // -param1 value1 --param2 /param3:"Test-:-work" 
            //   /param4=happy -param5 '--=nice=--'
            foreach(string Txt in Args)
            {
                // Look for new parameters (-,/ or --) and a
                // possible enclosed value (=,:)
                Parts = Spliter.Split(Txt,3);

                switch(Parts.Length){
                // Found a value (for the last parameter 
                // found (space separator))
                case 1:
                    if(Parameter != null)
                    {
                        if(!Parameters.ContainsKey(Parameter)) 
                        {
                            Parts[0] = 
                                Remover.Replace(Parts[0], "$1");

                            Parameters.Add(Parameter, Parts[0]);
                        }
                        Parameter=null;
                    }
                    // else Error: no parameter waiting for a value (skipped)
                    break;

                // Found just a parameter
                case 2:
                    // The last parameter is still waiting. 
                    // With no value, set it to true.
                    if(Parameter!=null)
                    {
                        if(!Parameters.ContainsKey(Parameter)) 
                            Parameters.Add(Parameter, "true");
                    }
                    Parameter=Parts[1];
                    break;

                // Parameter with enclosed value
                case 3:
                    // The last parameter is still waiting. 
                    // With no value, set it to true.
                    if(Parameter != null)
                    {
                        if(!Parameters.ContainsKey(Parameter)) 
                            Parameters.Add(Parameter, "true");
                    }

                    Parameter = Parts[1];

                    // Remove possible enclosing characters (",')
                    if(!Parameters.ContainsKey(Parameter))
                    {
                        Parts[2] = Remover.Replace(Parts[2], "$1");
                        Parameters.Add(Parameter, Parts[2]);
                    }

                    Parameter=null;
                    break;
                }
            }
            // In case a parameter is still waiting
            if(Parameter != null)
            {
                if(!Parameters.ContainsKey(Parameter)) 
                    Parameters.Add(Parameter, "true");
            }
        }

        // Retrieve a parameter value if it exists 
        // (overriding C# indexer property)
        public string this [string Param]
        {
            get
            {
                return(Parameters[Param]);
            }
        }
    }
}

The test class

Here is an example of how to use the Arguments class. As usual, the code is available in the zip file.

C#
using System;
using CommandLine.Utility;

namespace CommandLine
{
    /// <summary>
    /// Testing class
    /// </summary>
    class Test
    {
        /// <summary>
        /// Main loop
        /// </summary>
        [STAThread]
        static void Main(string[] Args)
        {
        // Command line parsing
        Arguments CommandLine=new Arguments(Args);

        // Look for specific arguments values and display 
        // them if they exist (return null if they don't)
        if(CommandLine["param1"] != null) 
            Console.WriteLine("Param1 value: " + 
                CommandLine["param1"]);
        else
            Console.WriteLine("Param1 not defined !");

        if(CommandLine["height"] != null) 
            Console.WriteLine("Height value: " + 
                CommandLine["height"]);
        else 
            Console.WriteLine("Height not defined !");

        if(CommandLine["width"] != null) 
            Console.WriteLine("Width value: " + 
                CommandLine["width"]);
        else 
            Console.WriteLine("Width not defined !");

        if(CommandLine["size"] != null) 
            Console.WriteLine("Size value: " + 
                CommandLine["size"]);
        else 
            Console.WriteLine("Size not defined !");

        if(CommandLine["debug"] != null) 
            Console.WriteLine("Debug value: " + 
                CommandLine["debug"]);
        else 
            Console.WriteLine("Debug not defined !");

        // Wait for key
        Console.Out.WriteLine("Arguments parsed. Press a key");
        Console.Read();
        }
    }
}

Execution sample

I provided the following command line as the Arguments setting in the properties dialog of the Visual Studio .NET solution included in the ZIP file: -size=100 /height:'400' -param1 "Nice stuff !" --debug that command line produced the following output

Param1 value: Nice stuff !
Height value: 400
Width not defined !
Size value: 100
Debug value: true
Arguments parsed. Press a key...

Conclusion

In this article with saw how to parse, store and retrieve the arguments line of a .NET application. This class is versatile enough to handle a lot of different kind of arguments. Compatible with most of the forms we are used to (-param, --param or Windows /param). Thanks to the regular expressions for this. Using regular expressions allowed us to keep the Arguments class very small and the usage is very simple. Regular expressions are very powerful and can be used for most of your parsing needs (as far as speed is not the main concern).

Special thanks to: Benjamin, Guillaume and Sebastien for their support :o)

Happy Coding!!!

History

  • October 28, 2002 - v1.0 Initial release
  • November 6, 2002 - v1.1 Bug fix. Should be bug free now. Thanks to n2s for help.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer (Senior) Siliconz Ltd
New Zealand New Zealand
Richard Lopes
Just Programmer

Comments and Discussions

 
QuestionPositional Parameters don't work Pin
snoopy00122-Feb-18 3:46
snoopy00122-Feb-18 3:46 
PraiseExcellent ! Pin
h1403024-Dec-15 15:16
h1403024-Dec-15 15:16 
QuestionArgs[0] as path Pin
NikolaB10-Nov-14 23:53
NikolaB10-Nov-14 23:53 
QuestionAnother solution Pin
Alan M Donnelly12-May-14 11:46
Alan M Donnelly12-May-14 11:46 
QuestionI love this command line parser Pin
raylitalo18-Feb-14 3:25
raylitalo18-Feb-14 3:25 
GeneralMy vote of 5 Pin
urinspiration7-Jan-14 21:51
urinspiration7-Jan-14 21:51 
GeneralMy vote of 5 Pin
Belgian Ducky12-Aug-13 22:19
Belgian Ducky12-Aug-13 22:19 
Questiongood Pin
Hua Yujun6-Apr-13 21:48
Hua Yujun6-Apr-13 21:48 
SuggestionJust 3 lines Pin
Sven Johannsen22-Mar-13 4:01
Sven Johannsen22-Mar-13 4:01 
QuestionCommand line parser with linq Pin
masterluke18-Jan-13 22:54
masterluke18-Jan-13 22:54 
GeneralMy vote of 5 Pin
Vincent Blais28-Nov-12 8:46
professionalVincent Blais28-Nov-12 8:46 
QuestionReally helpful Pin
missdeborah4-Jun-12 9:43
missdeborah4-Jun-12 9:43 
SuggestionAnother simple Command Line Arguments Parser Pin
Member 14778522-Mar-12 5:08
Member 14778522-Mar-12 5:08 
SuggestionArgument with space with file path as value fix PinPopular
tbare13-Sep-11 6:30
tbare13-Sep-11 6:30 
GeneralRe: Argument with space with file path as value fix Pin
hansmustermann9-Nov-12 3:59
hansmustermann9-Nov-12 3:59 
GeneralRe: Argument with space with file path as value fix Pin
Matthias Schett3-Apr-13 4:18
Matthias Schett3-Apr-13 4:18 
QuestionGreat class Pin
Shamaniac10-Jul-11 20:58
Shamaniac10-Jul-11 20:58 
GeneralVery Nicely Done!! Pin
raylitalo24-May-11 7:36
raylitalo24-May-11 7:36 
GeneralMy vote of 4 Pin
Lakonier2-May-11 21:44
Lakonier2-May-11 21:44 
GeneralRevised code, extension method-ified w/filepath support Pin
PeteM6-Oct-10 12:53
PeteM6-Oct-10 12:53 
Thanks for the jump off point Richard. Here's a revised version I annotated for my own understanding. It also handles the false-positive matching on parameter values that contain more than one valid delimiting character, even though the param value is enclosed in quotes:

public static class UtilClass
{
    // Valid parameters forms:
    // {-,/,--}param{ ,=,:}((",')value(",'))
    // Examples: 
    // -param1 value1 --param2 /param3:"Test-:-work" 
    //   /param4=happy -param5 '--=nice=--'
    public static StringDictionary SplitArgString(this string[] Args)
    {
        StringDictionary parameters = new StringDictionary();

        // Define -, --, /, : and = as valid delimiters.  Ignore : and = if enclosed in quotes.
        Regex validDelims = new Regex(@"^-{1,2}|^/|[^['""]?.*]=['""]?$|[^['""]?.*]:['""]?$", RegexOptions.IgnoreCase | RegexOptions.Compiled);

        // Define anything enclosed with double quotes as a match.  We'll use this to replace
        // the entire string with only the part that matches (everything but the quotes)
        Regex quotedString = new Regex(@"^['""]?(.*?)['""]?$", RegexOptions.IgnoreCase | RegexOptions.Compiled);

        string currentParam = null;
        string[] parts;

        foreach (string arg in Args)
        {
            // Apply validDelims to current arg to see how many significant characters were present
            // We're limiting to 3 to forcefully ignore any characters in the parameter VALUE
            // that would normally be used as a delimiter
            parts = validDelims.Split(arg, 3);

            switch (parts.Length)
            {
                // no special characters present.  we assume this means that this part
                // represents a value to the previously provided parameter.
                // For example, if we have: "--MyTestArg myValue"
                // currentParam would currently be set to "--MyTestArg"
                // parts[0] would hold "myValue", to be assigned to MyTestArg
                case 1:
                    if (currentParam != null)
                    {
                        if (!parameters.ContainsKey(currentParam))
                            parameters.Add(currentParam, quotedString.Replace(parts[0], "$1"));

                        currentParam = null;
                    }
                    break;

                // One split ocurred, meaning we found a parameter delimiter
                // at the start of arg, but nothing to denote a value.
                // example: --MyParam
                case 2:
                    // We already had a parameter with no value last time through the loop.
                    // That means we have no explicit value to give currentParam. We'll default it to "true"
                    if (currentParam != null && !parameters.ContainsKey(currentParam))
                        parameters.Add(currentParam, "true");

                    // Store our value-less param and grab the next arg to see if it has our value
                    // parts[0] only contains the opening delimiter -, --, or /, 
                    // so we go after parts[1] for the actual param name
                    currentParam = parts[1];
                    break;

                // Two splits occurred.  We found a starting parameter delimiter,
                // a parameter name, and another delimiter denoting a value for this parameter
                // Example: --MyParam=MyValue   or   --MyParam:MyValue
                case 3:
                    // We already had a parameter with no value last time through the loop.
                    // That means we have no explicit value to give currentParam. We'll default it to "true"
                    if (currentParam != null && !parameters.ContainsKey(currentParam))
                        parameters.Add(currentParam, "true");

                    // Store the good param name
                    currentParam = parts[1];

                    // Ignores parameters that have already been presented, not thrilled about this approach...
                    if (!parameters.ContainsKey(currentParam))
                        parameters.Add(currentParam, quotedString.Replace(parts[2], "$1"));

                    // Reset currentParam, we already have both parameter and value for this arg
                    currentParam = null;
                    break;
            }
        }

        // Final cleanup, we may still have a parameter at the end of the args string that didn't get a value
        if (currentParam != null)
        {
            if (!parameters.ContainsKey(currentParam))
                parameters.Add(currentParam, "true");
        }

        return parameters;
    }

}

GeneralRe: Revised code, extension method-ified w/filepath support Pin
tomidery14-Apr-11 4:41
tomidery14-Apr-11 4:41 
Generalpath as arguments Pin
roman30017824-Feb-10 3:13
roman30017824-Feb-10 3:13 
GeneralRe: path as arguments Pin
Tomazaz16-Mar-10 0:10
Tomazaz16-Mar-10 0:10 
AnswerRe: path as arguments Pin
kaepten10-Jun-10 19:52
kaepten10-Jun-10 19:52 
GeneralRe: path as arguments Pin
PeteM6-Oct-10 12:35
PeteM6-Oct-10 12:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.