Parsing command line arguments — CLI

In the last article I introduced JSAP, the command line parser from 2006. Now it is time for Commons CLI from Apache from the year 2009. And this is the last part of this article series.

Commons CLI

It is time to take a closer look at Apache’s CLI. It is an old tool but not as old as JSAP. Nevertheless I want to show you the same application I’ve shown you with JSAP however it is not possible. Let’s jump straight to this point: CLI does not have an unflagged option. So I cannot add the greedy option to swallow all remaining arguments and I cannot define the two required ones (file path and file name) as arguments without a flag. OK, it is possible but not as good as in JSAP. The usage I’ll show a bit later. But I do not let me down and I show you how can you wire up some simple argument parsing with apache CLI. First let me briefly recap what we need to do in the application (what arguments need to be parsed):

  • output path
  • output filename
  • connection timeout (optional)
  • proxy server host (optional)
  • proxy server port (optional)

Now we can look at CLI and compare the options we saw with JSAP.

Switch

Switches are simple options which do not require any arguments. This is the default behaviour of CLI, so if you define an option only with a flag you get a switch.

Option simpleSwitch = OptionBuilder.create("simpleSwitch");

Or if you do not want to interact with the OptionBuilder because you do not want your option as a required one (nor any other fancy properties) you can call:

options.addOption("timeout");

However the string parameter timeout in the addOptions method is the identifier of your option — and it defaults to the short flag. For a long flag you need to call the withLogOpt method providing the long name as a parameter.

Flagged option

Options in CLI are flagged. If you create any you have to provide a flag to it to do not get a compile error — as you could see above with switches. The only difference between a switch and a flagged option is that a flagged one has some arguments, which you can tell CLI with the hasArg() method. Optionally you can define multiple arguments to your flag, for this the value separator defaults to ‘=’ (equals symbol), but you can specify your own separator too (such as a colon or something).

The easiest way to create a flagged option is to call options.addOption:

options.addOption("t", "timeout", true, "Connection timeout in seconds. Optional.");

where the parameters are: short flag, long flag, hasArg, description.

Unflagged option

As I told you above there exists no feature such as unflagged options in CLI. However you can get the remaining parameters which do not belong to any flagged argument after the parsing. You can access those variables via the CommandLine.getArgList() method and iterate over the List of String objects. This solution could be used to determine the file name and output path if provided in order.

And I use this option because I eventually want my application to be backwards compatible — although this solution makes the whole application and argument-evaluation confusing. For details see the corresponding biz.hahamo.dev.commandLine.CLIExample class.

Example

After some shadowy words about how to create the different flags and options I will show how to tie the things up together.

Adding options to CLI

First of all you need an Options object where you can register all your options.

Options options = new Options();

If you have your options, you can add new options with the two methods mentioned in the Switch section:

options.addOption("t", "timeout", true, "Connection timeout in seconds. Optional.");

or for a future reference on your option object:

Option outputPath = OptionBuilder.hasArg().withLongOpt("timeout")
.withDescription("Connection timeout in seconds. Optional.").create("t");
options.addOption(timeout);

Validating your options against the provided arguments

If all options are set then you can validate the provided arguments against the options.

Fior this you need a CommandLineParser, I used the BasicParser implementation because it is just enough. The parse method requires the options and the arguments to parse. It returns a CommandLine object — or throws a ParseException if something bad happened while parsing.

final CommandLineParser parser = new BasicParser();
CommandLine cmd = parser.parse(options, args);

Using the parameters

After the parsing if no errors occurred and you’ve got a CommandLine object you want to use them. As you can assume the provided values are accessible through the CommandLine object with their short flag (or the ID). Here you can provide default values for your parameters if they are optional and you want to use them in any case.

String timeout = cmd.getOptionValue("t", "30");

The only problem is if you want to use a default value with a non-string value. There is no such option for the CLI utility. It is either. So in my use case if I would use CLI than I need to convert my timeout and proxy port parameters from the returned string values to integers. This is not the best solution I guess.

If you do not want to provide a default value you can simply call the getOptionValue method without the last parameter. To convert the parameter to another type of object, you first need to add the parameter type to the option, then call the getParsedOptionValue method of the CommandLine object. An example:

timeout.setType(Number.class);
cmd.getParsedOptionValue("t")

If your argument is optional or you defined an unknown type (the allowed types are String.class, Object.class, Number.class, Date.class, Class.class, FileInputStream.class, File.class, File[].class, URL.class) you get a null in return — so be aware and check for null in your optional parameters. But as I mentioned previously: there is no option for getting a formatted default value because CLI can convert your arguments to various formats.

If you need to handle the remaining parameters which have no flagged options you can do so calling getArgList() — which returns all arguments not parsed by the CommandLineParser. If you have some arguments which are not flagged but required you can access the remaining arguments as a String array too through the method getArgs(). So you can iterate over them in a normal for cycle. The example below shows how I extract the remaining unflagged options from the argument list:

System.out.println("Following extra parameters were provided:");
for (int i = 2; i < greedyValues.length; i++) {
    System.out.println(greedyValues[i]);
}

Error handling and printing the help

If you run into an error while parsing it is good to

  1. tell the user there is something wrong
  2. display the usage help

To handle the error part: if you parse arguments with the CommandLineParser you need to surround the block with a try-catch statement and handle ParseExceptions. Unfortunately the error message is not as clean as with JSAP: it lists only the missing parameters with their short option:

Missing required options: t

Again: if you have unflagged options which you want as required arguments: you need to handle it manually. Here you need to think out some clever mechanism, because you have to validate your parameters after CLI has done its. I give a basic variation which is barely good enough for my needs.

After handling the exceptions you should tell the user how to use the application. For this there is a handy HelpFormatter class where you can print the usage and the options of the application. What you need to provide is the command line call syntax and the Options object you defined your options in. CLI provides you an option where it can automatically print the flag-usage after the command line syntax — however if you have other options unflagged and required you should add them to your syntax.

In the case of this application printing the help is as follows:

final HelpFormatter helpFormatter = new HelpFormatter();
helpFormatter.printHelp("java -jar commandLine.jar <output path> <output filename>", options, true);

Conclusion

If you want to use unflagged and greedy arguments in your command line then you should definitely use JSAP — the older but more feature-rich solution from Martian Software.  This shows that sometimes Apache provides a tool what you need but if you look at other tools you’ll find that they have more power — or can handle your needs better. So an advice from me: Look through the woods of Apache.

Source code

As mostly the code for this article is available at GitHub.

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s