Parsing arguments to a Java command line program
What if I wanted to parse this:
java MyProgram -r opt1 -S opt2 arg1 arg2 arg3 arg4 --test -A opt3
And the result I want in my program is:
regular Java args[] of size=4
org.apache.commons.cli.Options[] of size=3
org.apache.commons.cli.Options[] #2 of size=1
I would prefer to use Apache Commons CLI, but the documentation is a little unclear about the case I pres开发者_如何学JAVAent above. Specifically, the documentation doesn't tell you how to handle options of the 3rd type I specify below:
1. options with a "-" char 2. options with a "--" char 3. options without any marker, or "bare args"
I wish that Apache Commons CLI would work but STILL be able to pass regular args to the program if those args didn't have a option prefix. Maybe it does but the documentation doesnt say so as I read through it...
Use the Apache Commons CLI library commandline.getArgs() to get arg1, arg2, arg3, and arg4. Here is some code:
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.Option.Builder;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.ParseException;
public static void main(String[] parameters)
{
CommandLine commandLine;
Option option_A = Option.builder("A")
.required(true)
.desc("The A option")
.longOpt("opt3")
.build();
Option option_r = Option.builder("r")
.required(true)
.desc("The r option")
.longOpt("opt1")
.build();
Option option_S = Option.builder("S")
.required(true)
.desc("The S option")
.longOpt("opt2")
.build();
Option option_test = Option.builder()
.required(true)
.desc("The test option")
.longOpt("test")
.build();
Options options = new Options();
CommandLineParser parser = new DefaultParser();
String[] testArgs =
{ "-r", "opt1", "-S", "opt2", "arg1", "arg2",
"arg3", "arg4", "--test", "-A", "opt3", };
options.addOption(option_A);
options.addOption(option_r);
options.addOption(option_S);
options.addOption(option_test);
try
{
commandLine = parser.parse(options, testArgs);
if (commandLine.hasOption("A"))
{
System.out.print("Option A is present. The value is: ");
System.out.println(commandLine.getOptionValue("A"));
}
if (commandLine.hasOption("r"))
{
System.out.print("Option r is present. The value is: ");
System.out.println(commandLine.getOptionValue("r"));
}
if (commandLine.hasOption("S"))
{
System.out.print("Option S is present. The value is: ");
System.out.println(commandLine.getOptionValue("S"));
}
if (commandLine.hasOption("test"))
{
System.out.println("Option test is present. This is a flag option.");
}
{
String[] remainder = commandLine.getArgs();
System.out.print("Remaining arguments: ");
for (String argument : remainder)
{
System.out.print(argument);
System.out.print(" ");
}
System.out.println();
}
}
catch (ParseException exception)
{
System.out.print("Parse error: ");
System.out.println(exception.getMessage());
}
}
You could just do it manually.
NB: might be better to use a HashMap instead of an inner class for the opts.
/** convenient "-flag opt" combination */
private class Option {
String flag, opt;
public Option(String flag, String opt) { this.flag = flag; this.opt = opt; }
}
static public void main(String[] args) {
List<String> argsList = new ArrayList<String>();
List<Option> optsList = new ArrayList<Option>();
List<String> doubleOptsList = new ArrayList<String>();
for (int i = 0; i < args.length; i++) {
switch (args[i].charAt(0)) {
case '-':
if (args[i].length < 2)
throw new IllegalArgumentException("Not a valid argument: "+args[i]);
if (args[i].charAt(1) == '-') {
if (args[i].length < 3)
throw new IllegalArgumentException("Not a valid argument: "+args[i]);
// --opt
doubleOptsList.add(args[i].substring(2, args[i].length));
} else {
if (args.length-1 == i)
throw new IllegalArgumentException("Expected arg after: "+args[i]);
// -opt
optsList.add(new Option(args[i], args[i+1]));
i++;
}
break;
default:
// arg
argsList.add(args[i]);
break;
}
}
// etc
}
I like this one. Simple, and you can have more than one parameter for each argument:
final Map<String, List<String>> params = new HashMap<>();
List<String> options = null;
for (int i = 0; i < args.length; i++) {
final String a = args[i];
if (a.charAt(0) == '-') {
if (a.length() < 2) {
System.err.println("Error at argument " + a);
return;
}
options = new ArrayList<>();
params.put(a.substring(1), options);
}
else if (options != null) {
options.add(a);
}
else {
System.err.println("Illegal parameter usage");
return;
}
}
For example:
-arg1 1 2 --arg2 3 4
System.out.print(params.get("arg1").get(0)); // 1
System.out.print(params.get("arg1").get(1)); // 2
System.out.print(params.get("-arg2").get(0)); // 3
System.out.print(params.get("-arg2").get(1)); // 4
I realize that the question mentions a preference for Commons CLI, but I guess that when this question was asked, there was not much choice in terms of Java command line parsing libraries. But nine years later, in 2020, would you not rather write code like the below?
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import java.io.File;
import java.util.List;
import java.util.concurrent.Callable;
@Command(name = "myprogram", mixinStandardHelpOptions = true,
description = "Does something useful.", version = "1.0")
public class MyProgram implements Callable<Integer> {
@Option(names = "-r", description = "The r option") String rValue;
@Option(names = "-S", description = "The S option") String sValue;
@Option(names = "-A", description = "The A file") File aFile;
@Option(names = "--test", description = "The test option") boolean test;
@Parameters(description = "Positional params") List<String> positional;
@Override
public Integer call() {
System.out.printf("-r=%s%n", rValue);
System.out.printf("-S=%s%n", sValue);
System.out.printf("-A=%s%n", aFile);
System.out.printf("--test=%s%n", test);
System.out.printf("positionals=%s%n", positional);
return 0;
}
public static void main(String... args) {
System.exit(new CommandLine(new MyProgram()).execute(args));
}
}
Execute by running the command in the question:
java MyProgram -r opt1 -S opt2 arg1 arg2 arg3 arg4 --test -A opt3
What I like about this code is that it is:
- compact - no boilerplate
- declarative - using annotations instead of a builder API
- strongly typed - annotated fields can be any type, not just String
- no duplication - option declaration and getting parse result are together in the annotated field
- clear - the annotations express the intention better than imperative code
- separation of concerns - the business logic in the
call
method is free of parsing-related logic - convenient - one line of code in main wires up the parser and runs the business logic in the Callable
- powerful - automatic usage and version help with the built-in
--help
and--version
options - user-friendly - usage help message uses colors to contrast important elements like option names from the rest of the usage help to reduce the cognitive load on the user
The above functionality is only part of what you get when you use the picocli (https://picocli.info) library.
Now, bear in mind that I am totally, completely, and utterly biased, being the author of picocli. :-) But I do believe that in 2020 we have better alternatives for building a command line apps than Commons CLI.
Here is @DwB solution upgraded to Commons CLI 1.3.1 compliance (replaced deprecated components OptionBuilder and GnuParser). The Apache documentation uses examples that in real life have unmarked/bare arguments but ignores them. Thanks @DwB for showing how it works.
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
public static void main(String[] parameters) {
CommandLine commandLine;
Option option_A = Option.builder("A").argName("opt3").hasArg().desc("The A option").build();
Option option_r = Option.builder("r").argName("opt1").hasArg().desc("The r option").build();
Option option_S = Option.builder("S").argName("opt2").hasArg().desc("The S option").build();
Option option_test = Option.builder().longOpt("test").desc("The test option").build();
Options options = new Options();
CommandLineParser parser = new DefaultParser();
options.addOption(option_A);
options.addOption(option_r);
options.addOption(option_S);
options.addOption(option_test);
String header = " [<arg1> [<arg2> [<arg3> ...\n Options, flags and arguments may be in any order";
String footer = "This is DwB's solution brought to Commons CLI 1.3.1 compliance (deprecated methods replaced)";
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("CLIsample", header, options, footer, true);
String[] testArgs =
{ "-r", "opt1", "-S", "opt2", "arg1", "arg2",
"arg3", "arg4", "--test", "-A", "opt3", };
try
{
commandLine = parser.parse(options, testArgs);
if (commandLine.hasOption("A"))
{
System.out.print("Option A is present. The value is: ");
System.out.println(commandLine.getOptionValue("A"));
}
if (commandLine.hasOption("r"))
{
System.out.print("Option r is present. The value is: ");
System.out.println(commandLine.getOptionValue("r"));
}
if (commandLine.hasOption("S"))
{
System.out.print("Option S is present. The value is: ");
System.out.println(commandLine.getOptionValue("S"));
}
if (commandLine.hasOption("test"))
{
System.out.println("Option test is present. This is a flag option.");
}
{
String[] remainder = commandLine.getArgs();
System.out.print("Remaining arguments: ");
for (String argument : remainder)
{
System.out.print(argument);
System.out.print(" ");
}
System.out.println();
}
}
catch (ParseException exception)
{
System.out.print("Parse error: ");
System.out.println(exception.getMessage());
}
}
Output:
usage: CLIsample [-A <opt3>] [-r <opt1>] [-S <opt2>] [--test]
[<arg1> [<arg2> [<arg3> ...
Options, flags and arguments may be in any order
-A <opt3> The A option
-r <opt1> The r option
-S <opt2> The S option
--test The test option
This is DwB's solution brought to Commons CLI 1.3.1 compliance (deprecated
methods replaced)
Option A is present. The value is: opt3
Option r is present. The value is: opt1
Option S is present. The value is: opt2
Option test is present. This is a flag option.
Remaining arguments: arg1 arg2 arg3 arg4
You could use https://github.com/jankroken/commandline , here's how to do that:
To make this example work, I must make assumptions about what the arguments means - just picking something here...
-r opt1 => replyAddress=opt1
-S opt2 arg1 arg2 arg3 arg4 => subjects=[opt2,arg1,arg2,arg3,arg4]
--test = test=true (default false)
-A opt3 => address=opt3
this can then be set up this way:
public class MyProgramOptions {
private String replyAddress;
private String address;
private List<String> subjects;
private boolean test = false;
@ShortSwitch("r")
@LongSwitch("replyAddress") // if you also want a long variant. This can be skipped
@SingleArgument
public void setReplyAddress(String replyAddress) {
this.replyAddress = replyAddress;
}
@ShortSwitch("S")
@AllAvailableArguments
public void setSubjects(List<String> subjects) {
this.subjects = subjects;
}
@LongSwitch("test")
@Toggle(true)
public void setTest(boolean test) {
this.test = test;
}
@ShortSwitch("A")
@SingleArgument
public void setAddress(String address) {
this.address = address;
}
// getters...
}
and then in the main method, you can just do:
public final static void main(String[] args) {
try {
MyProgramOptions options = CommandLineParser.parse(MyProgramOptions.class, args, OptionStyle.SIMPLE);
// and then you can pass options to your application logic...
} catch
...
}
}
You could use the refcodes-console
artifact at refcodes-console on REFCODES.ORG:
Option<String> r = new StringOptionImpl( "-r", null, "opt1", "..." );
Option<String> s = new StringOptionImpl( "-S", null, "opt2", "..." );
Operand<String> arg1 = new StringOperandImpl( "arg1", "..." );
Operand<String> arg2 = new StringOperandImpl( "arg2", "..." );
Operand<String> arg3 = new StringOperandImpl( "arg3", "..." );
Operand<String> arg4 = new StringOperandImpl( "arg4", "..." );
Switch test = new SwitchImpl( null, "--test", "..." );
Option<String> a = new StringOptionImpl( "-A", null, "opt3", "..." );
Condition theRoot = new AndConditionImpl( r, s, a, arg1, arg2, arg3, arg4,
test );
Create your arguments parser ArgsParserImpl
with your root condition:
ArgsParser theArgsParser = new ArgsParserImpl( theRoot );
theArgsParser.setName( "MyProgramm" );
theArgsParser.setSyntaxNotation( SyntaxNotation.GNU_POSIX );
Above you define your syntax, below you invoke the parser:
theArgsParser.printUsage();
theArgsParser.printSeparatorLn();
theArgsParser.printOptions();
theArgsParser.evalArgs( new String[] {
"-r", "RRRRR", "-S", "SSSSS", "11111", "22222", "33333", "44444",
"--test", "-A", "AAAAA"
} );
In case you provided some good descriptions, theArgsParser.printUsage()
will even show you the pretty printed usage:
Usage: MyProgramm -r <opt1> -S <opt2> -A <opt3> arg1 arg2 arg3 arg4 --test
In the above example all defined arguments must be passed by the user, else the parser will detect a wrong usage. In case the
--test
switch is to be optional (or any other argument), assigntheRoot
as follows:
theRoot = new AndConditionImpl( r, s, a, arg1, arg2, arg3, arg4, new OptionalImpl( test ) );
Then your syntax looks as follows:
Usage: MyProgramm -r <opt1> -S <opt2> -A <opt3> arg1 arg2 arg3 arg4 [--test]
The full example for your case you find in the StackOverFlowExamle. You can use AND, OR, XOR conditions and any kind of nesting ... hope this helps.
Evaluate the parsed arguments as follows:
r.getValue() );
orif (test.getValue() == true) ...
:
LOGGER.info( "r :=" + r.getValue() );
LOGGER.info( "S :=" + s.getValue() );
LOGGER.info( "arg1 :=" + arg1.getValue() );
LOGGER.info( "arg2 :=" + arg2.getValue() );
LOGGER.info( "arg3 :=" + arg3.getValue() );
LOGGER.info( "arg4 :=" + arg4.getValue() );
LOGGER.info( "test :=" + test.getValue() + "" );
LOGGER.info( "A :=" + a.getValue() );
Ok, thanks to Charles Goodwin for the concept. Here is the answer:
import java.util.*;
public class Test {
public static void main(String[] args) {
List<String> argsList = new ArrayList<String>();
List<String> optsList = new ArrayList<String>();
List<String> doubleOptsList = new ArrayList<String>();
for (int i=0; i < args.length; i++) {
switch (args[i].charAt(0)) {
case '-':
if (args[i].charAt(1) == '-') {
int len = 0;
String argstring = args[i].toString();
len = argstring.length();
System.out.println("Found double dash with command " +
argstring.substring(2, len) );
doubleOptsList.add(argstring.substring(2, len));
} else {
System.out.println("Found dash with command " +
args[i].charAt(1) + " and value " + args[i+1] );
i= i+1;
optsList.add(args[i]);
}
break;
default:
System.out.println("Add a default arg." );
argsList.add(args[i]);
break;
}
}
}
}
Simple code for command line in java:
class CMDLineArgument
{
public static void main(String args[])
{
String name=args[0];
System.out.println(name);
}
}
精彩评论