Click here to Skip to main content
Click here to Skip to main content

C#/.NET Command Line Argument Parser Reloaded

By , 9 Sep 2011
 

Introduction

Many programs use command line arguments. Each time parsing has to be done in the same way. There are other projects which facilitate the parsing process. For example, C#/.NET Command Line Arguments Parser does a great job, however it lacks verification of the passed and parsed arguments. C# command-line option parser can do that, however using it is quite complicated. CLAParser is a compromise of both: It is easy to use and uses regular expressions for parsing and validating which makes it quite powerful. Further more, it supports i18n and it creates information for correct command line usage for the user.

Naming Conventions

  • Arguments: Everything passed on argument line to program separated by spaces, or grouped by quotes, e.g.
    program.exe argument1 /argument2 
    	--argument3 .!§$argument4´ß2! "argument5 with spaces"
  • Parameter: All arguments that start with / or -, e.g.
    program.exe /parameter1 -parameter2
  • Values: Every argument that is not a parameter, e.g.
    program.exe value1 value2 /my_param  'v a l u e 3'

In the last example, value1 and value2 are values without parameters, whereas v a l u e 3 belongs to parameter my_param. (Note that the single quotes which hold 'v a l u e 3' together are removed by CLAParser.)

Using the Code

  1. Copy CLAParser files to your project folder and include them in C# project (CmdLineArgumentParser.cs, CmdLineArgumentParserRes.resx, and optionally further resx-files).

    project explorer

  2. Create a CLAParser instance by calling the constructor.
    CLAParser.CLAParser CmdLine = new CLAParser.CLAParser("CLAParserTest");
    The argument must be the default namespace. (This is necessary so that CLAParser can find its resx-files.)
  3. Use CmdLine.Parameter() function to define known parameters.
    CmdLine.Parameter(CLAParser.CLAParser.ParamAllowType.Optional,
        "", CLAParser.CLAParser.ValueType.String,
        "Path to file that is to be loaded on starting the program.");
    CmdLine.Parameter(CLAParser.CLAParser.ParamAllowType.Optional,
        "output", CLAParser.CLAParser.ValueType.OptionalString,
        "Write output to file, if no file specified default file output.txt is used.");
    CmdLine.Parameter(CLAParser.CLAParser.ParamAllowType.Required, "add",
        CLAParser.CLAParser.ValueType.MultipleInts,
        "Do a mathematical addition of number given integers.");
  4. Start parsing by calling function CmdLine.Parse(). Raised exceptions can be passed directly to the user. They indicate the reason for the problem. Further, show user correct syntax and information about the parameters using CmdLine.GetUsage() and CmdLine.GetParameterInfo().
    try
    {
        CmdLine.Parse();
    }
    catch (CLAParser.CLAParser.CmdLineArgumentException Ex)
    {
        Console.WriteLine(Ex.Message);
        Console.WriteLine(CmdLine.GetUsage());
        Console.WriteLine(CmdLine.GetParameterInfo());
    }
  5. If arguments are correct and all required arguments are defined, no exception is raised and values of all parameters can be accessed by the dictionary interface. Optional parameters which are not defined by the user are indicated by null values. A value without parameter can be accessed by the empty string ("").
    if(CmdLine[""] != null)
    {
        value = CmdLine[""];
        ...
    }
    if(CmdLine["parameter"] != null)
    {
        value = CmdLine["parameter"];
        ...
    }

Points of Interest

  • Update: Using the obsolete function Parse(string[] Arguments) with args as parameter quotes (") must be escaped (\") to be recognized correctly. It is recommended to use function Parse() instead!
  • Function FindMismatchReasonInRegex() helps in analyzing why a search string does not match a regular expression and indicates where the problem probably is located.
  • CLAParser is internationalized, i.e. new languages can easily be defined by creating new resx-files (localization).
  • Using the enumerator, all arguments can be traversed, e.g.:
    IEnumerator e = CmdLine.GetEnumerator();
    while (e.MoveNext())
    {
        DictionaryEntry arg = (DictionaryEntry)e.Current;
        Console.WriteLine(arg.Key + "=" + arg.Value);
    }
  • For a complete overview of CLAParser's capabilities, have a look at the attached code files.

History

  • 2010-02-24
    • Initial release
  • 2010-04-05
    • CmdLineArgumentParser.cs:574 -> from "...Parameters == false)" to "...Parameters == true)"
  • 2011-01-06
    • Corrected error in regular expression which made it impossible to use more than one quoted value
    • Added internal function GetRawCommandlineArgs() proposed by vermis0. Together with it comes function Parse() which makes function Parse(string[] Arguments) obsolete. Now the quotes of quoted values (e.g. "value with spaces") do not need to be escaped anymore!
  • 2011-01-07
    • Corrected error in regular expression which made it impossible to use a value without parameter containing minus (-) or slash (/).
    • Made behaviour with (first) value without parameter more consistent. Instead of setting AllowValuesWithoutParameter, use function Parameter() with ParameterName=="".
    • Fixed error with case sensitivity of parameter. Now everything is case insensitive.
  • 2011-09-09
    • Corrected problem reported by Hautzendorfer concerning single parameters not being processed correctly

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Julian Ohrt
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
SuggestionSimple Command Line Arguments ParsermemberMember 14778522 Mar '12 - 5:31 
If you don't use complex command line arguments, consider use of a very Simple Command Line Arguments Parser[^]. It can be used as a foundation for your own application specific parameter presenter.
QuestionSomething more FunctionalmemberAlois Kraus9 Sep '11 - 12:26 
Everybody has written a command line parser. I did create a console application template with a command line parser which is I believe the most simple and easy to understand one.
It goes basically like
public  Program(string [] args)
         {
             // define parameterless command line switches.  
             // Please note: Upper case characters define the shortcut name for each switch 
             var  switches = new  Dictionary 
             {
                 {"OptionaL" , () => Optional=true  }, // shortcut -ol 
             };
 
             // define command line switches which take one parameter 
             var  switchWithArg = new  Dictionary >
             {
                 {"ArgSwitch" , (arg) => ArgSwitch = arg },  // shortcut -AS 
             };
 

             // Handler for  if present 
             Action > rest = (parameters) => OtherArgs = parameters;
 
             ArgParser  parser = new  ArgParser (switches, switchWithArg, rest);
 

             // check if command line is well formed 
             if  (!parser.ParseArgs(args))
             {
                 parser.PrintHelpWithMessages(HelpStr);
                 return ;
             }
 
             if  (!ValidateArgs(parser))
             {
                 // Display errors but not the help screen. 
                 parser.PrintHelpWithMessages(null );
             }
             else 
             {
                 // Ok we can start 
                 Run();
             }
         }
 
Here is a more thorough explanation:
 
http://geekswithblogs.net/akraus1/archive/2010/11/11/142685.aspx[^]
AnswerRe: Something more FunctionalmemberJulian Ohrt9 Sep '11 - 23:47 
You are right, there are tons of command line parsers! Thank for sharing yours. It is very nice. Thumbs Up | :thumbsup:
 
Which one is easier to use is just personal taste I would say. I like the shortcuts of your parser, whereas with CLAParser I like that you do not have to define variables for each argument as well as the integrated construction of the help output.
 
Which one is more functional I do not dare to say. My guess is that generic but simple approaches - like ours - always come with some limitation. E.g. mine cannot handle two files with spaces, like prog.exe "file 1" "file 2"; yours cannot handle switches after other arguments, like prog.exe file.txt /a, can it?
GeneralRe: Something more FunctionalmemberAlois Kraus9 Sep '11 - 23:58 
Yes you are right there is no right parser for everybody. As you correctly pointed out each one has its own set of limitations.
 
Yours,
Alois Kraus
GeneralIsn't there a problem ...memberHautzendorfer23 May '11 - 9:54 
Hello Julian,
 
I tried to parse program.exe /help and got some exception:
Ex.Message = "Command Line Argument Error: Value without parameter found: \"/help\". Each value must be assigned to a parameter!"
 
I figured out, that parsing created an unknown value, but it should be a parameter.
 
If e.g. some parameter has been matched before the valueless parameter, it works fine (e.g. program.exe /all 1 2 /help
 
So I modified the regex for:
4) or anything that contains no space [^ ]*?
to:
4) or anything that contains no space but does not start with / or - [[^/^-]^ ]*? ... valueless parameter (e.h. /help).
 
... this works fine for program.exe /help
 
Hope I didn't miss a thing ...
 
Regards and thanks for posting it,
Martin
GeneralRe: Isn't there a problem ...memberJulian Ohrt9 Sep '11 - 2:55 
Hi Martin,
 
thanks for your post! A bummer you found there! OMG | :OMG:
 
However, hanging the regex to ignore leading / and - as you propose would mean that you cannot specify an absolute (Linux style) path, e.g: program.exe /etc/usr/
Well, it might be unlikely that anybody would want to do it, nevertheless, I don't like to swallow anything there.
 
I think its a structural problem you detected and solving it right would involve not distinguishing between the first parameter and following parameter-value-pairs. But since that would - no doubt - trigger further changes, I came up with a work around. It involves re-regexing the first detected parameter. Not very nice but it should work. The article and the download archive is being updated shortly.
 
Thanks again!
Julian
QuestionFxCop CompliancememberDave Hary8 Jan '11 - 8:13 
I like the implementation. Have you checked the code for FxCop compliance?
AnswerRe: FxCop Compliancememberleppie9 Jan '11 - 23:04 
Dave Hary wrote:
Have you checked the code for FxCop compliance?

 
Who gives a sh$t about FxCop? If you want FxCop compliance, surely you can do it yourself.

AnswerRe: FxCop CompliancememberJulian Ohrt9 Sep '11 - 2:59 
Hi Dave,
 
thanks for the suggestion. It might be nice to have the code checked by FxCop. However, it has (at this time) no priority for me. Sorry.
 
Regards,
Julian
GeneralSuggestion on using Environment.CommandLinemembervermis06 Dec '10 - 9:53 
You might want to consider working with a more "raw" version of the command line that goes into your .Parse so you can have more consistent parsing. If you use the snippet below, you won't have to worry about what pre-processing .NET might have done to the args[] (like string escape handling). I ran out of time so I don't know what code changes are required to support this in your library.
 
/// <summary>This will return the raw unprocessed command line parameters as a string.</summary>
/// <returns></returns>
/// <remarks>Essentially, we are taking the unprocessed CommandLine and removing the executable path prefix by comparison to the .NET processed CommandLineArg[0].</remarks>
static public string GetRawCommandlineArgs() {			
  string Raw = Environment.CommandLine;
  string ExecutablePath = Environment.GetCommandLineArgs()[0];
  string Parsed;
 
  // Raw is the completely unprocessed commandline the OS used to launch our application. This includes the
  // path used to launch the executable as well as the parameters.

  // Our ExecutablePath is the .NET parsed path to the executable used to launch this application.
  // It can be a relative or absolute path. Since it was parsed by .NET, if the OS passed the executable
  // path as an encapsulated string, the surrounding " have been removed.
  //
  // If the raw commandline starts with a ", then we need to take that into account when we chop off
  // the executable path prefix (it was parsed out by .NET in ExecutablePath).
  if (Raw.StartsWith("\"")) {
    Parsed = Raw.Substring(ExecutablePath.Length + 2);
  } else {
    Parsed = Raw.Substring(ExecutablePath.Length);
  }
 
  // Trim any leading spaces (there can be one or two depending on how the executable was launched)
  Parsed = Parsed.TrimStart(' ');
 
  return Parsed;
}
 
The whole reason I went down this path is because for the life of me I couldn't figure out how to pass these parameters!
 
This will return 'Unknown parameter found: "01\product;Integrated".'

my.exe /ConnectionString "Data Source=Server-01\product;Integrated Security=true;" /ScriptFile "file-00.ext"

 

This will return 'A required parameter is missing! Parameter "ScriptFile" is required but missing!'

my.exe /ConnectionString "\"Data Source=Server-01\product;Integrated Security=true;\"" /ScriptFile "\"file-00.ext\""

GeneralRe: Suggestion on using Environment.CommandLinememberHeinz JKarl Otta Fritz6 Jan '11 - 3:11 
I am sorry. You are right. There was an error in the regular expression which caused you problem. Otherwise your example should have worked.
An update is on its way. It also includes your function GetRawCommandlineArgs(). Please use function Parse() now (without parameter). Thank you very much for your help!
GeneralWorks greatmemberssm629716 Jul '10 - 6:21 
This is exactly what I as looking for. Saved me half a day on a project that needed to be finished yesterday. Big Grin | :-D
QuestionWhat about...memberkornman005 Mar '10 - 5:40 
Nice article, I like what you've got brewing
 
However, what about a console application which performs multiple functions with their own arguments? Maybe I played an ignorant eye but it seems that the parser is only for a single set of arguments/values. So what if there are different contexts the console app can take?
 
Example, you have an application which processes intermediate data for various input data (geometry, sound, images, etc) which require their own respective arguments? What about having factory of a sort (so you're only defining one entry-point object) which you can define these contexts (and thus their arguments) with? If in the event the application only has one purpose you could have a "default" property for a context which it defaults to.
 
Basically the first argument would define the context which to look up (ie, import-sound) then any following arguments would just act as that specific context's arguments and thus the parsing would execute on.
AnswerRe: What about...memberMarkLTX5 Mar '10 - 6:33 
You mean like the "net" command? For example, "net accounts" has a completely different set of parameters vs. "net config". Just entering "net" produces this:
 
NET [ ACCOUNTS | COMPUTER | CONFIG | CONTINUE | FILE | GROUP | HELP |
HELPMSG | LOCALGROUP | NAME | PAUSE | PRINT | SEND | SESSION |
SHARE | START | STATISTICS | STOP | TIME | USE | USER | VIEW ]
GeneralRe: What about...memberkornman005 Mar '10 - 7:16 
Exactly
GeneralRe: What about...memberHeinz JKarl Otta Fritz5 Apr '10 - 3:09 
> it seems that the parser is only for a single set of arguments/values
 
Yes, that is correct. There are also other scenarios which are not covered directly by this parser. It is supposed to be small and easy to use. I fear, including more functions and features would make it too complicated.
 
However, there is an easy solution for your problem:
Create one instance of CLAParser to find out the context. Like:
CmdLine.Parameter(CLAParser.CLAParser.ParamAllowType.Optional, "ACCOUNTS", CLAParser.CLAParser.ValueType.Bool, "help text...");
CmdLine.Parameter(CLAParser.CLAParser.ParamAllowType.Optional, "COMPUTER ", CLAParser.CLAParser.ValueType.Bool, "help text...");
CmdLine.AllowAdditionalParameters = true;
...
Then create for each context one other instance of CLAParser. Like:
if(CmdLine["ACCOUNTS"])
{
  CLAParser.CLAParser CmdLineACCOUNTS = new CLAParser.CLAParser("CLAParserTest");
  //define sub-parameters for ACCOUNT context
  CmdLineACCOUNTS.AllowAdditionalParameters = false;
} else if(CmdLine["COMPUTER"])
{
  CLAParser.CLAParser CmdLineCOMPUTER = new CLAParser.CLAParser("CLAParserTest");
  //define sub-parameters for COMPUTER context
  CmdLineCOMPUTER.AllowAdditionalParameters = false;
} else 
{
  Console.WriteLine(CmdLine.GetUsage());
}
modified on Wednesday, April 7, 2010 9:28 AM

GeneralI like what you have done, but alas someone beat you to itmvpSacha Barber5 Mar '10 - 1:18 
Have a look at
 
Check out : http://niagara.codeplex.com/SourceControl/changeset/view/31682#691364 / NiagaraDataServiceUtil folder
 
Still I like what you have done, and can totally see this being useful, so have made your article public and voted it a 5
Sacha Barber
  • Microsoft Visual C# MVP 2008/2009
  • Codeproject MVP 2008/2009
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralRe: I like what you have done, but alas someone beat you to itmemberHeinz JKarl Otta Fritz5 Apr '10 - 3:16 
Thanks for your reply! I think I even stumbled over that one before. However it lacks verification of the passed and parsed arguments. At least I do not see how to define which parameters are optional and which are required. That is why I wanted to make my own (for my feeling) more intuitive parser.

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

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 9 Sep 2011
Article Copyright 2010 by Julian Ohrt
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid