5

I want to parse a set of command line arguments that look like:

-p[project file path] -s[name 1]=[value 1] ... -s[name n]=[value n] 

Where there is exactly one project p and any number of settings s.

I have tried using NDesk.Options

var set = new OptionSet { { "p=", "the project file", v => { /* do stuff */ } }, { "s=", "a setting", (m, v) => { /* do stuff */ } }, }; 

and this works well in most cases, but when value is a file path (even quoted) the \ causes the parser to drop everything to right. I've hacked round this by overriding the parse method on my own OptionSet class that I've inherited from NDesk.Options.OptionSet, but I was wondering if there are any libraries that can handle this kind of functionality out of the box?

UPDATE

Sorry it wasn't the \ I think it is the : anyway a set of failing examples is:

-sSetting=C:\Temp -sSetting="C:\Temp" -s"Setting=C:\Temp" 

They all fail with OptionException Error: Found 3 option values when expecting 2.

1
  • can you give an example command line that fails? Did you quote the whole argument, or only the filename part? Commented Apr 19, 2013 at 10:23

5 Answers 5

5

UPDATE: Update to handle colons in setting values.

OK, so here you run into one of the implicit defaults of NDesk.Options, which is that in multivalued parameters, both : and = are considered as value separators, meaning that Setting=C:\Path parses as 3 values (Setting, C, \Path) instead of your expected two.

In order to fix that, you simply have to modify the -s option definition to only consider = as a valid separator, by writing "s={=}" instead of "s=".

Original answer, when it was about backslashes.

I have used NDesk.Options, without encountering any issues with quoted paths and backslashes.

Here is my sample program:

public static void Main(string[] args) { string parsedPath = null; Dictionary<string, string> parsedValues = new Dictionary<string, string>(); var set = new OptionSet() { { "p=", "the project path", v => parsedPath = v }, { "s=", "a setting", (m, v) => { parsedValues.Add(m, v); } }, }; set.Parse(args); Console.WriteLine(parsedPath ?? "<NULL>"); foreach (var keyValuePair in parsedValues) { Console.WriteLine(keyValuePair.Key + "::::" + keyValuePair.Value); } } 

You will see that there is a difference between your definition and mine: p= means that the option has a required value, while your definition means that p is a boolean flag value.

I have not run with any problem concerning backslashes, either in the p setting or in the s setting. Could you try running the program with version 0.2.1 of NDesk.Options and show which values fail?

Here are some samples that I ran, which all parsed successfully:

-p=..\Path -p..\Path -pC:\Hello -pHello\World -p"Hello\World" -s"Greeting=Hello\World" -sGreeting="Hello\World" -sGreeting=Hello\World -sGreeting="Hello\My World" -s"Greeting=Hello\My World" 

Here are some parses which do produce another result that deserve mention:

-sGreeting=Hello\My World -- // This gives Greeting="Hello\My" 

Note: If that changes anything, I ran NDesk.Options with the Options.cs source code file in the project, not with the compiled DLL.

Sign up to request clarification or add additional context in comments.

5 Comments

adding some spaces to your test cases wouldn't hurt
@CodesInChaos: Obviously it would, and I added them, but at that stage I could see that I did not have an obvious issue with NDesk.Options that even remotely looked like his description - that everything right of the backslash is dropped, so I might as well ask what his issue was.
Thanks, will try when I'm back at the office on Monday and accept if it works (guess it does though so cheers). Out of interest where is that feature documented; I read through all of the documentation I could find and don't remember seeing that?
I found it by reading the Options.cs file's header (Look for "key/value separator"), but I agree that I have not found any other source for this feature.
Thanks - you are more persistent than I am!
3

I successfully used Command Line Parser Library for some time. Works well, simple, and paths with quotes are allowed.

Comments

2

All the splitting is done for you if you use something like:

public static void Main(string[] args) { List<string> Settings = new list Console.WriteLine("parameter qty = {0}", args.Length); for(int i = 0; i < args.Length; i++) { Console.WriteLine("Arg[{0}] = [{1}]", i, args[i]); } 

After that a simple set of ifs should suffice to get you the required action based on each argument. You can iterate over each argument in the argument array to see what arg they conform to using string matching or regexes as you prefer. For eaxmple you can add some code in the for loop:

public static void Main(string[] args) { List<string> Settings = new List<string>(); Console.WriteLine("parameter qty = {0}", args.Length); for(int i = 0; i < args.Length; i++) { Console.WriteLine("Arg[{0}] = [{1}]", i, args[i]); if (i>0) { // this gives you a nice iterable list of settings Setting.Add(args[i]); } } foreach(string setting in Settings) { //do the desired action } } 

Addendum: this will, however, provide only basic functionality(which is fine if like me you're from C++ and have to do things yourself, not so good from a C# developer point of view I now realise), you would need to handle parsing of variant commands yourself (as mentioned in the comment /p--p or /Project= -Project= and variants would all need to be handled in code you craft). However for an out of the box solution I would recommend: Ndesk.Options or Mono.Options Both work similarly, and I would tend to Mono.Options if portability were important.

In use you can change your code to

var set = new OptionSet { { "p=|project=", "the project file", v => { /* do stuff */ } }, { "s=|setting=", "a setting", (m, v) => { /* do stuff */ } }, }; 

And that should hopefully give you the kind of functionality you want (example from ndesk here at the end of the page there is even a coded exemplar use).

Hope this is more useful than my previous answer for you.

5 Comments

That would be a good answer if splitting strings from args[] into buckets was all that the command line parsing "libraries" did. But with your answer, the asker still has to take out the "-p" and "-s" prefix strings himself (and neither alternative forms /p,--p or aliases /project=,--project= are handled), and then he has to parse out the "=" sign in all settings, and then he has to write all the code to produce the command-line documentation.
@JeanHominal updated to reflect your pointers and the question specifically more closely:)
Actually, Mono.Options is pretty much the same code as NDesk.Options - both are as portable as the other (You will see that in the code of Mono.Options, there is an NDESK_OPTIONS compilation variable, whose only effect is to change the namespace of the class). However, I can see now that Mono.Options has been updated 5 months ago, while the NDesk.Options page has apparently not moved since 2008. I guess then that Mono.Options has slightly more features than NDesk.Options.
-1: Do not declare the prefixes in the option set - they are automatically recognized. (i.e., write "p=|project=" and "s=|setting="), and all of -p,/p,--p, with or without =, will be recognized.
@JeanHominal thanks for the corrections, politely done and succint:)
0

You can joinh all arguments in a single string and then you can use regular expressions:

((?<SkeyvaluePair>-s(?<key>[^=]+)\s*=\s*(?<value>[^-]+))|(?<PkeyvaluePair>-p(?<Pvalue>[^-]+)))* 

then split the string into groups

var groups = new Regex(yourRegex).Match(message).Groups; 

and than extract the information you need

var pairs = groups["keyvaluepair"].Captures.Cast<Capture>() 

Comments

0
class Program { static void Main(string[] args) { string cmd = "car.exe -ip 1.18.4.156 -port 123"; string hostname = "localhost"; string port = "5505"; string[] array = cmd.Split(' '); int hostnameIndex = Array.FindIndex(array, key => key == "-ip"); int portIndex = Array.FindLastIndex(array, key => key == "-port"); if (hostnameIndex != -1) { hostname = array[hostnameIndex + 1]; } if (portIndex != -1) { port = array[portIndex + 1]; } Console.WriteLine("ip :" + hostname); Console.WriteLine("port :" + port); } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.