Parsing simple command line arguments in C#
For a long time, I never needed to parse command line arguments, because I never wrote anything meaningful that would require them. Hey! I'm a hobbyist, not a professional dev!
Then I started to write some tools to help my colleagues, and some tools needed to run on a company server, and be integrated with some PowerShell scripts for further treatment. So I needed to adapt my code to make my application silent and make it return a meaningful exit code.
So I made my application accept a parameter to run it silently. And then I needed more parameters for the next program, and then more, and so on...
This post will be quite short, and show how I evolved in my approach of command line arguments.
Let's start quickly: I am not good with array manipulation (well, I'm not good with a lot of things to be frank). So I was looking for some tools to help me. I found this one, appropriately named CommandLine, that did a lot of the heavy work. A quick NuGet install, a glance at the documentation, and in 5 minutes, you're good to go.
For example, I wanted my application to do the following:
- require a username
- require a password
- select a distant database
- be silent or not (do not show any output the console)
- preserve the application logs or not
If you can count, that makes 5 parameters, including 2 required ones. Those parameters will be used to set the value of private variables down the line.
With CommandLine, it turned out to be quite easy, and looks like that:
using CommandLine;
...
private class Options
{
[Option('u', "username", Required = true, HelpText = "Database username.")]
public string UserName { get; set; }
[Option('p', "password", Required = true, HelpText = "Database password.")]
public string Password { get; set; }
[Option("preserve-log", Required = false, HelpText = "Use this option to preserve old log files")]
public bool PreserveLog { get; set; }
[Option("silent", Required = false, HelpText ="If this parameter is given, application will not output any text.\r\nLog file and/or exit code will have to be read for details.")]
public bool Silent { get; set; }
[Option('t', "tsf", Required = false, HelpText = "If this parameter is given, application will use the TSF Database")]
public bool Tsf { get; set; }
}
If we look at this class, you see that the decorations are quite explicit: a short name, that will catch the parameters with a single dash (eg -u
, a long name, that will be caught with a double dash (eg --tsf
), and a help text, that will be displayed if either you use the --help
parameter (automatically managed by CommandLine), or forget a required argument. And you define at this stage the type of the parameters, in my case string
for the username and password, and bool
for the others.
Then, in the Main function, the use is as follow (preferably before any other code, but that depends on your logic):
Parser.Default.ParseArguments<Options>(args)
.WithParsed(options =>
{
_username = options.UserName;
_password = options.Password;
_preserveLog = options.PreserveLog;
_silent = options.Silent;
_tsf = options.Tsf;
})
.WithNotParsed(options =>
{
Log("Missing required arguments, cannot continue.", true);
Exit(ExitCodes.BadCommandLineArguments);
});
The .WithParsed
is used when all required options are there, and .WithNotparsed
if something's wrong. In my case, I don't want to continue if either username or password are missing. My application will connect to a webserver that requires an authentication, so there is no need to continue if you don't provide your login info. And the function Exit()
is a custom one, related to the point I made in the intro about managing the exit codes.
And that's all you need.
Then you run you application, CommandLine manages the parsing of the parameters for you, and you're golden.
BUT!
There was one issue for me. CommandLine comes in the form a dll that needs to be distributed with your application. And the binary for my app was 25kB, and CommandLine.dll is about 220kB. Let's be honest, I don't think anyone likes that size difference. Side note, I had the same issue with NewtonSoft.Json, that is almost 700kB, but that's another issue.
So, I decided to put my code under a strict diet, and came up with a very basic, but I believe efficient, solution. Here's my solution, in all its quick-and-dirty beauty:
private static void ParseArguments(string[] args)
{
if (args.Length == 0 ||
(!args.Contains("-u") && !args.Contains("--username")) ||
(!args.Contains("-p") && !args.Contains("--password")))
{
Log("Missing required arguments, cannot continue.", true);
Console.WriteLine(@"ERROR(S):
REQUIRED parameter 'u, username' or 'p, password' is missing is missing.
Parameters:
-u, --username Required. Hexagon username.
-p, --password Required. Hexagon password.
-t, --tsf If this parameter is given, application will use the TSF Database.
Can be combined with other parameters.
--silent If this parameter is given, application will not output any text.
Log file and/or exit code will have to be read for details.
Can be combined with other parameters.
--preserve-log Use this option to preserve old log files
Can be combined with other parameters.");
Exit(ExitCodes.BadCommandLineArguments);
}
for (int i = 0; i < args.Length - 1; i++)
{
switch (args[i])
{
case "-u":
case "--username":
_username = args[i + 1];
break;
case "-p":
case "--password":
_password = args[i + 1];
break;
case "-t":
case "--tsf":
_tsf = true;
break;
case "--silent":
_silent = true;
break;
case "--preserve-log":
_preserveLog = true;
break;
}
}
}
What's there? We give it the arguments (args[]
), do a quick check for empty arguments, quick check if there is username and password argument, and then, a simple switch to set the proper flags and values. I don't care if the user uses calls myProgram -u -p --silent
and forgets to put his username and password. I could do a check, but the execution will just fail down the line with a reasonable exit code to allow for troubleshooting, soooo... No, I do not need more.
And whabam, that's how I remove 75% of the disk space of my application.
Was it necessary? Absolutely not.
Could I have lived with CommandLine? Absolutely yes.
Would I recommend CommandLine? Definitely yes!
Was it worth it? No, no it's not, but hey! I do me, you do you!
Ajouter un commentaire