Click here to Skip to main content
Email Password   helpLost your password?

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:

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.

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.

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

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
Generalpath as arguments
roman300178
4:13 24 Feb '10  
I have trouble to define parameters with path values.
For example:

--path d:\temp\file.txt

or

--path "d:\temp\file.txt"

or

--path 'd:\temp\file.txt'
GeneralRe: path as arguments
Tomazaz
1:10 16 Mar '10  
Yes, this parser is totally sux, it do not work with file paths, useless.
GeneralGreat Tool
ariight
13:03 27 Oct '09  
This is excellent. Thank you Big Grin
GeneralMy updated version
Jake Ginnivan
20:07 26 Jul '09  
I have been playing with your original command line parser, it did not quite meet my needs so I have been tweaking it.

http://jake.ginnivan.net/2009/07/c-argument-parser/[^]

Thanks heaps for the original code it is a very nice way to approach the problem.
GeneralRe: My updated version
GriffonRL
20:34 26 Jul '09  
Hi,

All the pleasure is mine. Feel free to use it.
I would recommend all interested people to look at your updated version.

Thanks for you words,

Richard


GeneralInteresting mix of comments!
hughd
1:40 12 Jul '09  
I thought i'd just add my comment, which is I've been using an "MS" one for years, and it seems solid and powerful. It does automatic shorthands, and optons file. Here's a link.

http://www.koders.com/csharp/fid9953548D02FEE3252D7CBA336A4C6346DF9B0B56.aspx[^]

You define a class
public class CLIArgs
{
public string ConnectionStr = string.Empty;
public string ScriptsFolder = string.Empty;
}

and then do somehting like this

CLIArgs parsedArgs = new CLIArgs();
if (!CommandLine.Parser.ParseArguments(args, parsedArgs))
{
Console.WriteLine(CommandLine.Parser.ArgumentsUsage(typeof(CLIArgs)));
Console.Write("\n");
Console.Write("Blah blah blah\n");
return;
}

if (parsedArgs.ConnectionStr.Length == 0)
{
Console.WriteLine("ConnectionStr must be specified");
return;
}
GeneralWhy to submit buggy code?
Tomazaz
4:04 30 Jan '09  
Can I ask you why you submit buggy code and even do not have time to correct it in six year?
Do not confuse other developers and keep you code for yourself.
GeneralThis dude stole your work and then copyrighted it?
mike.griffin@entityspaces.net
9:35 8 Jan '09  
http://www.dreamincode.net/forums/showtopic47895.htm[^]
GeneralRe: This dude stole your work and then copyrighted it?
mike.griffin@entityspaces.net
9:37 8 Jan '09  
Or is this you, his name is Richard L. McCutchen, is that L for Lopes?
GeneralRe: This dude stole your work and then copyrighted it?
GriffonRL
9:53 8 Jan '09  
Hi,

Firstable I am glad to meet you since I evaluated entityspaces a while back and loved it. Very goos work.
And to answer your question: no I am not Richard L. McCutchen. L. is not for Lopes at all. I'm going to look at his website.

Best regards,

R. LOPES
Just programmer.
Http://sili.co.nz/blog

GeneralRe: This dude stole your work and then copyrighted it?
GriffonRL
10:05 8 Jan '09  
Hi,

Thank you for finding this complete ripoff.
I'm not even angry, I'm really sorry for the guy. He even put a copyright in it indeed and moreover a more restrictive GPL license. Also it says created in "March 2008".
So I am going to warn the guy and notify the website to remove the article.

Thank you again very much,

R. LOPES
Just programmer.
Http://sili.co.nz/blog

GeneralRe: This dude stole your work and then copyrighted it?
mike.griffin@entityspaces.net
10:53 8 Jan '09  
I am about to create our command line version for ES2009 and went looking for a sample. I found yours, went to look at a few more articles, found his and was like what the heck? By the way, ES2009 runs under Visual Studio and doesn't require MyGeneration or CodeSmith anymore (sorry for the commerical). I really thought in the end his article was yours since the names seemed to almost match.

- Mike
GeneralRe: This dude stole your work and then copyrighted it?
GriffonRL
11:46 8 Jan '09  
Hi,

Is there already a trial for ES2009 ?
I forget to look even if I just finished reading the ES2009 features and some of your blog posts. I saw there was one trial for ES2008 but this one still rely on MyGeneration. Actually MyGeneration is great, I used it for many tasks in the past. Of course for the OR/M, Visual Studio integration is a definitive plus.

By the way I responded to the guy that ripped off my article, and I posted that: http://sili.co.nz/blog/2009/01/hello-you-have-been-ripped-off/

Thanks again,

R. LOPES
Just programmer.
Http://sili.co.nz/blog

GeneralWindows XP anomaly and fix
Todd Smith
14:15 8 Oct '08  
I found that on XP the inclusion of double quotes "" would cause separate command line options to be merged into a single command line argument passed into Main.

For example:
myapp.exe /dir:"C:\program files\myapp\data" /confirm:true /server:localhost
on XP void Main(string[] args) would contain only two arguments.
args[0] myapp.exe
args[1] /dir:"C:\program files\myapp\data" /confirm:true /server:localhost
The only way to make it work on XP was to replace the double quotes with single quotes
myapp.exe /dir:'C:\program files\myapp\data' /confirm:true /server:localhost
which would then correclty return
args[0] myapp.exe
args[1] /dir:'C:\program files\myapp\data' args[2] /confirm:true /server:localhost
args[3] /server:localhost


Todd Smith

GeneralNConsoler - another command line parsing library
tihobrazov
7:54 8 Sep '08  
NConsoler is an open source library that provides command line parser functionality based on attribute metadata attached to type.
Library is very easy to add and use in your application. NConsoler gives an ability to display help and validation messages without any line of code.

http://nconsoler.csharpus.com/

Example code:

using System;
using NConsoler;

public class Program {
public static void Main(params string[] args) {
Consolery.Run(typeof(Program), args);
}

[Action]
public static void Method(
[Required] string name,
[Optional(true)] bool flag) {
Console.WriteLine("name: {0}, flag: {1}", name, flag);
}
}

and use it:

program.exe "Maxim" /-flag
Generalwhat could be better
teetrinker7003
6:32 26 Jun '08  
Hey, thank you very much!

But I have changed my code to inheritance:
public class Arguments : Dictionary<string,>
Like that I can use all the functionality of a Dictionary class. That makes it much easier. That didn't needed a lot of changes in the using code, but it gives a lot more comfort.

Greets tt


--------------

the whole code:
using System.Text.RegularExpressions;
using System.Collections.Generic;

namespace CommandLine.Utility
{
/// /// Arguments class
///
public class Arguments : Dictionary<string,>
{
// Constructor
public Arguments(string[] Args)
{
Regex splitter = 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 = splitter.Split(Txt, 3);

switch (parts.Length)
{
// Found a value (for the last parameter
// found (space separator))
case 1:
if (parameter != null)
{
if (!base.ContainsKey(parameter))
{
parts[0] = remover.Replace(parts[0], "$1");
base.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 (!base.ContainsKey(parameter))
{
base.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 (!base.ContainsKey(parameter))
base.Add(parameter, "true");
}

parameter = parts[1];

// Remove possible enclosing characters (",')
if (!base.ContainsKey(parameter))
{
parts[2] = remover.Replace(parts[2], "$1");
base.Add(parameter, parts[2]);
}
parameter = null;
break;
}
}
// In case a parameter is still waiting
if (parameter != null)
{
if (!base.ContainsKey(parameter))
{
base.Add(parameter, "true");
}
}
}
}
}

General;) [modified]
ITMaiO
22:44 15 May '08  
Great peace of code.

What about adding a prop Count, so we can check in our apps

Arguments CommandLine = new Arguments(args);

if (CommandLine.Count > 0)
// do parm proccessing
else Console.WriteLine("No Parms Defined");




TNX!

modified on Friday, May 16, 2008 5:31 AM

QuestionLicense Type
bhargav_j
13:19 25 Mar '08  
Hi,

I wanted to use your code in my application and I wanted to know what are the restrictions on the usage. Please let me know the type of license you have attached to this code.

Thank you
QuestionRe: License Type
Peter Kneale
1:30 24 Aug '08  
Any mention of the license type? I'm finding this handy too and want to use it in a small tool im writing.
Thanks
AnswerRe: License Type
GriffonRL
1:39 24 Aug '08  
Hi there,

Thank you.
The license is: "free to use and to modify". I don't any license on these piece of code. If you want to, you can mention me Wink .
All my new stuff use MIT license and is this also very permissive even for commercial applications.

Cheers,

Richard LOPES
Just programmer.

GeneralCan you please update the code
JoeBob101
11:20 27 Sep '07  
I tried the suggestion to remove |: from the Splitter regex and it does fix file name issue but now it can not read value of height defined as /height:'400' on the command line.

Also someone else suggested to make change in the regex. I can't find the original statement in the code, nor does the mod work.

Any help is much appreciated.
GeneralExcellent
Lu Yixiang
20:15 4 Jun '07  
Very powerful and easy to use.


Never trust a computer you can't throw out a window.

GeneralGreat code bud.
mrsnipey
18:09 3 Jun '07  
Saved me hours of work and it works first go.
Have done this before many times in the old UNIX days
and didn't really want to go through it all again.

Thanks.
Wink
GeneralThanks for this code!
patiman
4:47 21 May '07  
I am new to C# (old time C coder) and remember what a pain it was to parse args[]... this was a 10 minute setup (I had to figure out how to use "Project...Add existing item..." to add the .cs file to my project and w/o any code changes it just worked exactly how I needed it to.

Thanks!
JokeHurray for the French
Pinx
5:03 11 Apr '07  
This is a great little piece of code. Thumbs up for that.

It's also a beautiful illustration of the reputation of "the French way of doing things".
In the Netherlands we have a proverb about this.
I just hope people interested in this code have the time to go through all the comments, because without them, you might lose a lot of time debugging.

We also have a saying "Live like God in France", so it must be an enjoyable country to live in. Or at least at some point in time.


Last Updated 6 Nov 2002 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010