简体   繁体   中英

How to parse command line arguments

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.

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.

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

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.

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>() 
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);

        }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM